From 748b258797dec2798a707345b1de1c750ebdbe25 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:40:19 -0300 Subject: [PATCH] feat(crate): Support `releaseTimestamp` (#31467) Co-authored-by: Rhys Arkins <rhys@arkins.net> --- lib/modules/datasource/crate/index.spec.ts | 42 ++++++++++++++++++++++ lib/modules/datasource/crate/index.ts | 36 ++++++++++++++++++- lib/modules/datasource/crate/schema.ts | 11 ++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 lib/modules/datasource/crate/schema.ts diff --git a/lib/modules/datasource/crate/index.spec.ts b/lib/modules/datasource/crate/index.spec.ts index c01dd03a0a..765bba4d61 100644 --- a/lib/modules/datasource/crate/index.spec.ts +++ b/lib/modules/datasource/crate/index.spec.ts @@ -389,4 +389,46 @@ describe('modules/datasource/crate/index', () => { ).toReject(); }); }); + + describe('postprocessRelease', () => { + const datasource = new CrateDatasource(); + + it('no-op for registries other than crates.io', async () => { + const releaseOrig = { version: '4.5.17' }; + + const res = await datasource.postprocessRelease( + { + packageName: 'clap', + registryUrl: 'https://example.com', + }, + releaseOrig, + ); + + expect(res).toBe(releaseOrig); + }); + + it('fetches releaseTimestamp', async () => { + httpMock + .scope(API_BASE_URL) + .get('/crates/clap/4.5.17') + .reply(200, { + version: { + created_at: '2024-09-04T19:16:41.355243+00:00', + }, + }); + + const res = await datasource.postprocessRelease( + { + packageName: 'clap', + registryUrl: 'https://crates.io', + }, + { version: '4.5.17' }, + ); + + expect(res).toEqual({ + version: '4.5.17', + releaseTimestamp: '2024-09-04T19:16:41.355243+00:00', + }); + }); + }); }); diff --git a/lib/modules/datasource/crate/index.ts b/lib/modules/datasource/crate/index.ts index 24ec9ce47e..29eb0b8498 100644 --- a/lib/modules/datasource/crate/index.ts +++ b/lib/modules/datasource/crate/index.ts @@ -11,7 +11,14 @@ import { newlineRegex, regEx } from '../../../util/regex'; import { joinUrlParts, parseUrl } from '../../../util/url'; import * as cargoVersioning from '../../versioning/cargo'; import { Datasource } from '../datasource'; -import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import type { + GetReleasesConfig, + PostprocessReleaseConfig, + PostprocessReleaseResult, + Release, + ReleaseResult, +} from '../types'; +import { ReleaseTimestampSchema } from './schema'; import type { CrateMetadata, CrateRecord, @@ -371,4 +378,31 @@ export class CrateDatasource extends Datasource { return [packageName.slice(0, 2), packageName.slice(2, 4), packageName]; } + + @cache({ + namespace: `datasource-crate`, + key: ( + { registryUrl, packageName }: PostprocessReleaseConfig, + { version }: Release, + ) => `postprocessRelease:${registryUrl}:${packageName}:${version}`, + ttlMinutes: 7 * 24 * 60, + cacheable: ({ registryUrl }: PostprocessReleaseConfig, _: Release) => + registryUrl === 'https://crates.io', + }) + override async postprocessRelease( + { packageName, registryUrl }: PostprocessReleaseConfig, + release: Release, + ): Promise<PostprocessReleaseResult> { + if (registryUrl !== 'https://crates.io') { + return release; + } + + const url = `https://crates.io/api/v1/crates/${packageName}/${release.version}`; + const { body: releaseTimestamp } = await this.http.getJson( + url, + ReleaseTimestampSchema, + ); + release.releaseTimestamp = releaseTimestamp; + return release; + } } diff --git a/lib/modules/datasource/crate/schema.ts b/lib/modules/datasource/crate/schema.ts new file mode 100644 index 0000000000..9d799be31b --- /dev/null +++ b/lib/modules/datasource/crate/schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const ReleaseTimestampSchema = z + .object({ + version: z.object({ + created_at: z.string(), + }), + }) + .transform(({ version: { created_at } }) => created_at) + .nullable() + .catch(null); -- GitLab