From bf57547e55892eb7a1752ebd8b56e2cb89ec0db4 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Thu, 15 Jun 2023 12:27:03 +0300 Subject: [PATCH] refactor(rubygems): Remove unnecessary datasource class (#22785) --- lib/modules/datasource/rubygems/get.ts | 208 ---------------------- lib/modules/datasource/rubygems/index.ts | 124 +++++++++++-- lib/modules/datasource/rubygems/schema.ts | 82 +++++++++ 3 files changed, 194 insertions(+), 220 deletions(-) delete mode 100644 lib/modules/datasource/rubygems/get.ts create mode 100644 lib/modules/datasource/rubygems/schema.ts diff --git a/lib/modules/datasource/rubygems/get.ts b/lib/modules/datasource/rubygems/get.ts deleted file mode 100644 index 87e658b86f..0000000000 --- a/lib/modules/datasource/rubygems/get.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Marshal } from '@qnighy/marshal'; -import is from '@sindresorhus/is'; -import { z } from 'zod'; -import { logger } from '../../../logger'; -import { HttpError } from '../../../util/http'; -import { LooseArray } from '../../../util/schema-utils'; -import { getQueryString, joinUrlParts, parseUrl } from '../../../util/url'; -import { Datasource } from '../datasource'; -import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; - -const MarshalledVersionInfo = LooseArray( - z - .object({ - number: z.string(), - }) - .transform(({ number: version }) => ({ version })) -) - .transform((releases) => (releases.length === 0 ? null : { releases })) - .nullable() - .catch(null); - -const GemsInfo = z - .object({ - name: z.string().transform((x) => x.toLowerCase()), - version: z.string().nullish().catch(null), - changelog_uri: z.string().nullish().catch(null), - homepage_uri: z.string().nullish().catch(null), - source_code_uri: z.string().nullish().catch(null), - }) - .transform( - ({ - name: packageName, - version, - changelog_uri: changelogUrl, - homepage_uri: homepage, - source_code_uri: sourceUrl, - }) => ({ - packageName, - version, - changelogUrl, - homepage, - sourceUrl, - }) - ); -type GemsInfo = z.infer<typeof GemsInfo>; - -const GemVersions = LooseArray( - z - .object({ - number: z.string(), - created_at: z.string(), - platform: z.string().nullable().catch(null), - ruby_version: z.string().nullable().catch(null), - rubygems_version: z.string().nullable().catch(null), - }) - .transform( - ({ - number: version, - created_at: releaseTimestamp, - platform, - ruby_version: rubyVersion, - rubygems_version: rubygemsVersion, - }): Release => { - const result: Release = { version, releaseTimestamp }; - const constraints: Record<string, string[]> = {}; - - if (platform) { - constraints.platform = [platform]; - } - - if (rubyVersion) { - constraints.ruby = [rubyVersion]; - } - - if (rubygemsVersion) { - constraints.rubygems = [rubygemsVersion]; - } - - if (!is.emptyObject(constraints)) { - result.constraints = constraints; - } - - return result; - } - ) -); -type GemVersions = z.infer<typeof GemVersions>; - -export class InternalRubyGemsDatasource extends Datasource { - constructor(override readonly id: string) { - super(id); - } - - async getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> { - const registryUrl = config.registryUrl; - // istanbul ignore if - if (!registryUrl) { - return null; - } - - const packageName = config.packageName.toLowerCase(); - - const hostname = parseUrl(registryUrl)?.hostname; - return hostname === 'rubygems.pkg.github.com' || hostname === 'gitlab.com' - ? await this.getDependencyFallback(registryUrl, packageName) - : await this.getDependency(registryUrl, packageName); - } - - async getDependencyFallback( - registryUrl: string, - packageName: string - ): Promise<ReleaseResult | null> { - const path = joinUrlParts(registryUrl, `/api/v1/dependencies`); - const query = getQueryString({ gems: packageName }); - const url = `${path}?${query}`; - const { body: buffer } = await this.http.getBuffer(url); - const data = Marshal.parse(buffer); - return MarshalledVersionInfo.parse(data); - } - - async fetchGemsInfo( - registryUrl: string, - packageName: string - ): Promise<GemsInfo | null> { - try { - const { body } = await this.http.getJson( - joinUrlParts(registryUrl, '/api/v1/gems', `${packageName}.json`), - GemsInfo - ); - return body; - } catch (err) { - // fallback to deps api on 404 - if (err instanceof HttpError && err.response?.statusCode === 404) { - return null; - } - throw err; - } - } - - async fetchGemVersions( - registryUrl: string, - packageName: string - ): Promise<GemVersions | null> { - try { - const { body } = await this.http.getJson( - joinUrlParts(registryUrl, '/api/v1/versions', `${packageName}.json`), - GemVersions - ); - return body; - } catch (err) { - if (err.statusCode === 400 || err.statusCode === 404) { - logger.debug( - { registry: registryUrl }, - 'versions endpoint returns error - falling back to info endpoint' - ); - return null; - } else { - throw err; - } - } - } - - async getDependency( - registryUrl: string, - packageName: string - ): Promise<ReleaseResult | null> { - const info = await this.fetchGemsInfo(registryUrl, packageName); - if (!info) { - return await this.getDependencyFallback(registryUrl, packageName); - } - - if (info.packageName !== packageName) { - logger.warn( - { lookup: packageName, returned: info.packageName }, - 'Lookup name does not match the returned name.' - ); - return null; - } - - let releases: Release[] | null = null; - const gemVersions = await this.fetchGemVersions(registryUrl, packageName); - if (gemVersions?.length) { - releases = gemVersions; - } else if (info.version) { - releases = [{ version: info.version }]; - } - - if (!releases) { - return null; - } - - const result: ReleaseResult = { releases }; - - if (info.changelogUrl) { - result.changelogUrl = info.changelogUrl; - } - - if (info.homepage) { - result.homepage = info.homepage; - } - - if (info.sourceUrl) { - result.sourceUrl = info.sourceUrl; - } - - return result; - } -} diff --git a/lib/modules/datasource/rubygems/index.ts b/lib/modules/datasource/rubygems/index.ts index 3d141d8678..1c58d762d6 100644 --- a/lib/modules/datasource/rubygems/index.ts +++ b/lib/modules/datasource/rubygems/index.ts @@ -1,10 +1,13 @@ +import { Marshal } from '@qnighy/marshal'; import { PAGE_NOT_FOUND_ERROR } from '../../../constants/error-messages'; +import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; -import { parseUrl } from '../../../util/url'; +import { HttpError } from '../../../util/http'; +import { getQueryString, joinUrlParts, parseUrl } from '../../../util/url'; import * as rubyVersioning from '../../versioning/ruby'; import { Datasource } from '../datasource'; -import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { InternalRubyGemsDatasource } from './get'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import { GemVersions, GemsInfo, MarshalledVersionInfo } from './schema'; import { VersionsDatasource } from './versions-datasource'; export class RubyGemsDatasource extends Datasource { @@ -13,9 +16,6 @@ export class RubyGemsDatasource extends Datasource { constructor() { super(RubyGemsDatasource.id); this.versionsDatasource = new VersionsDatasource(RubyGemsDatasource.id); - this.internalRubyGemsDatasource = new InternalRubyGemsDatasource( - RubyGemsDatasource.id - ); } override readonly defaultRegistryUrls = ['https://rubygems.org']; @@ -26,8 +26,6 @@ export class RubyGemsDatasource extends Datasource { private readonly versionsDatasource: VersionsDatasource; - private readonly internalRubyGemsDatasource: InternalRubyGemsDatasource; - @cache({ namespace: `datasource-${RubyGemsDatasource.id}`, key: ({ registryUrl, packageName }: GetReleasesConfig) => @@ -54,12 +52,114 @@ export class RubyGemsDatasource extends Datasource { error.message === PAGE_NOT_FOUND_ERROR && parseUrl(registryUrl)?.hostname !== 'rubygems.org' ) { - return this.internalRubyGemsDatasource.getReleases({ - packageName, - registryUrl, - }); + const pkgName = packageName.toLowerCase(); + const hostname = parseUrl(registryUrl)?.hostname; + return hostname === 'rubygems.pkg.github.com' || + hostname === 'gitlab.com' + ? await this.getDependencyFallback(registryUrl, pkgName) + : await this.getDependency(registryUrl, pkgName); } throw error; } } + + async getDependencyFallback( + registryUrl: string, + packageName: string + ): Promise<ReleaseResult | null> { + const path = joinUrlParts(registryUrl, `/api/v1/dependencies`); + const query = getQueryString({ gems: packageName }); + const url = `${path}?${query}`; + const { body: buffer } = await this.http.getBuffer(url); + const data = Marshal.parse(buffer); + return MarshalledVersionInfo.parse(data); + } + + async fetchGemsInfo( + registryUrl: string, + packageName: string + ): Promise<GemsInfo | null> { + try { + const { body } = await this.http.getJson( + joinUrlParts(registryUrl, '/api/v1/gems', `${packageName}.json`), + GemsInfo + ); + return body; + } catch (err) { + // fallback to deps api on 404 + if (err instanceof HttpError && err.response?.statusCode === 404) { + return null; + } + throw err; + } + } + + async fetchGemVersions( + registryUrl: string, + packageName: string + ): Promise<GemVersions | null> { + try { + const { body } = await this.http.getJson( + joinUrlParts(registryUrl, '/api/v1/versions', `${packageName}.json`), + GemVersions + ); + return body; + } catch (err) { + if (err.statusCode === 400 || err.statusCode === 404) { + logger.debug( + { registry: registryUrl }, + 'versions endpoint returns error - falling back to info endpoint' + ); + return null; + } else { + throw err; + } + } + } + + async getDependency( + registryUrl: string, + packageName: string + ): Promise<ReleaseResult | null> { + const info = await this.fetchGemsInfo(registryUrl, packageName); + if (!info) { + return await this.getDependencyFallback(registryUrl, packageName); + } + + if (info.packageName !== packageName) { + logger.warn( + { lookup: packageName, returned: info.packageName }, + 'Lookup name does not match the returned name.' + ); + return null; + } + + let releases: Release[] | null = null; + const gemVersions = await this.fetchGemVersions(registryUrl, packageName); + if (gemVersions?.length) { + releases = gemVersions; + } else if (info.version) { + releases = [{ version: info.version }]; + } + + if (!releases) { + return null; + } + + const result: ReleaseResult = { releases }; + + if (info.changelogUrl) { + result.changelogUrl = info.changelogUrl; + } + + if (info.homepage) { + result.homepage = info.homepage; + } + + if (info.sourceUrl) { + result.sourceUrl = info.sourceUrl; + } + + return result; + } } diff --git a/lib/modules/datasource/rubygems/schema.ts b/lib/modules/datasource/rubygems/schema.ts new file mode 100644 index 0000000000..3931a40c0c --- /dev/null +++ b/lib/modules/datasource/rubygems/schema.ts @@ -0,0 +1,82 @@ +import is from '@sindresorhus/is'; +import { z } from 'zod'; +import { LooseArray } from '../../../util/schema-utils'; +import type { Release } from '../types'; + +export const MarshalledVersionInfo = LooseArray( + z + .object({ + number: z.string(), + }) + .transform(({ number: version }) => ({ version })) +) + .transform((releases) => (releases.length === 0 ? null : { releases })) + .nullable() + .catch(null); + +export const GemsInfo = z + .object({ + name: z.string().transform((x) => x.toLowerCase()), + version: z.string().nullish().catch(null), + changelog_uri: z.string().nullish().catch(null), + homepage_uri: z.string().nullish().catch(null), + source_code_uri: z.string().nullish().catch(null), + }) + .transform( + ({ + name: packageName, + version, + changelog_uri: changelogUrl, + homepage_uri: homepage, + source_code_uri: sourceUrl, + }) => ({ + packageName, + version, + changelogUrl, + homepage, + sourceUrl, + }) + ); +export type GemsInfo = z.infer<typeof GemsInfo>; + +export const GemVersions = LooseArray( + z + .object({ + number: z.string(), + created_at: z.string(), + platform: z.string().nullable().catch(null), + ruby_version: z.string().nullable().catch(null), + rubygems_version: z.string().nullable().catch(null), + }) + .transform( + ({ + number: version, + created_at: releaseTimestamp, + platform, + ruby_version: rubyVersion, + rubygems_version: rubygemsVersion, + }): Release => { + const result: Release = { version, releaseTimestamp }; + const constraints: Record<string, string[]> = {}; + + if (platform) { + constraints.platform = [platform]; + } + + if (rubyVersion) { + constraints.ruby = [rubyVersion]; + } + + if (rubygemsVersion) { + constraints.rubygems = [rubygemsVersion]; + } + + if (!is.emptyObject(constraints)) { + result.constraints = constraints; + } + + return result; + } + ) +); +export type GemVersions = z.infer<typeof GemVersions>; -- GitLab