From 2336161d05a3e4ff0b950e10d955c6b590d83e26 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Sun, 5 Feb 2023 10:20:07 +0300 Subject: [PATCH] fix(packagist): Use schema for `extractDepReleases` method (#20034) --- .../datasource/packagist/index.spec.ts | 8 +-- lib/modules/datasource/packagist/index.ts | 44 +++++++------- .../datasource/packagist/schema.spec.ts | 59 +++++++++++++++---- lib/modules/datasource/packagist/schema.ts | 41 +++++++++---- lib/modules/datasource/packagist/types.ts | 2 +- 5 files changed, 104 insertions(+), 50 deletions(-) diff --git a/lib/modules/datasource/packagist/index.spec.ts b/lib/modules/datasource/packagist/index.spec.ts index 4ef75442e1..88d6775fe1 100644 --- a/lib/modules/datasource/packagist/index.spec.ts +++ b/lib/modules/datasource/packagist/index.spec.ts @@ -57,10 +57,10 @@ describe('modules/datasource/packagist/index', () => { const packagesOnly = { packages: { 'vendor/package-name': { - 'dev-master': {}, - '1.0.x-dev': {}, - '0.0.1': {}, - '1.0.0': {}, + 'dev-master': { version: 'dev-master' }, + '1.0.x-dev': { version: '1.0.x-dev' }, + '0.0.1': { version: '0.0.1' }, + '1.0.0': { version: '1.0.0' }, }, }, }; diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts index eb4a57056a..43024bbb3b 100644 --- a/lib/modules/datasource/packagist/index.ts +++ b/lib/modules/datasource/packagist/index.ts @@ -5,7 +5,6 @@ import { cache } from '../../../util/cache/package/decorator'; import * as hostRules from '../../../util/host-rules'; import type { HttpOptions } from '../../../util/http/types'; import * as p from '../../../util/promises'; -import { regEx } from '../../../util/regex'; import { ensureTrailingSlash, joinUrlParts } from '../../../util/url'; import * as composerVersioning from '../../versioning/composer'; import { Datasource } from '../datasource'; @@ -119,27 +118,28 @@ export class PackagistDatasource extends Datasource { return packagistFile; } - private static extractDepReleases(versions: RegistryFile): ReleaseResult { - const dep: ReleaseResult = { releases: [] }; - // istanbul ignore if - if (!versions) { - return dep; + /* istanbul ignore next */ + private static extractDepReleases( + composerReleases: unknown + ): ReleaseResult | null { + const parsedRecord = + schema.ComposerReleasesRecord.safeParse(composerReleases); + if (parsedRecord.success) { + return schema.extractReleaseResult(Object.values(parsedRecord.data)); } - dep.releases = Object.keys(versions).map((version) => { - // TODO: fix function parameter type: `versions` - const release = (versions as any)[version]; - const parsedVersion = release.version ?? version; - dep.homepage = release.homepage || dep.homepage; - if (release.source?.url) { - dep.sourceUrl = release.source.url; - } - return { - version: parsedVersion.replace(regEx(/^v/), ''), - gitRef: parsedVersion, - releaseTimestamp: release.time, - }; - }); - return dep; + + const parsedArray = + schema.ComposerReleasesArray.safeParse(composerReleases); + if (parsedArray.success) { + logger.once.info('Packagist: extracting releases from array'); + return schema.extractReleaseResult(parsedArray.data); + } + + logger.once.info( + { composerReleases }, + 'Packagist: unknown format to extract from' + ); + return null; } @cache({ @@ -162,7 +162,7 @@ export class PackagistDatasource extends Datasource { providerPackages, } = registryMeta; - const includesPackages: Record<string, ReleaseResult> = {}; + const includesPackages: Record<string, ReleaseResult | null> = {}; const tasks: (() => Promise<void>)[] = []; diff --git a/lib/modules/datasource/packagist/schema.spec.ts b/lib/modules/datasource/packagist/schema.spec.ts index 4a4cca0390..0180a1ab9c 100644 --- a/lib/modules/datasource/packagist/schema.spec.ts +++ b/lib/modules/datasource/packagist/schema.spec.ts @@ -1,7 +1,8 @@ import type { ReleaseResult } from '../types'; import { ComposerRelease, - ComposerReleases, + ComposerReleasesArray, + ComposerReleasesRecord, MinifiedArray, parsePackagesResponse, parsePackagesResponses, @@ -110,25 +111,57 @@ describe('modules/datasource/packagist/schema', () => { }); }); - describe('ComposerReleases', () => { - it('rejects ComposerReleases', () => { - expect(() => ComposerReleases.parse(null)).toThrow(); - expect(() => ComposerReleases.parse(undefined)).toThrow(); - expect(() => ComposerReleases.parse('')).toThrow(); - expect(() => ComposerReleases.parse({})).toThrow(); + describe('ComposerReleasesArray', () => { + it('rejects ComposerReleasesArray', () => { + expect(() => ComposerReleasesArray.parse(null)).toThrow(); + expect(() => ComposerReleasesArray.parse(undefined)).toThrow(); + expect(() => ComposerReleasesArray.parse('')).toThrow(); + expect(() => ComposerReleasesArray.parse({})).toThrow(); }); - it('parses ComposerReleases', () => { - expect(ComposerReleases.parse([])).toEqual([]); - expect(ComposerReleases.parse([null])).toEqual([]); - expect(ComposerReleases.parse([1, 2, 3])).toEqual([]); - expect(ComposerReleases.parse(['foobar'])).toEqual([]); + it('parses ComposerReleasesArray', () => { + expect(ComposerReleasesArray.parse([])).toEqual([]); + expect(ComposerReleasesArray.parse([null])).toEqual([]); + expect(ComposerReleasesArray.parse([1, 2, 3])).toEqual([]); + expect(ComposerReleasesArray.parse(['foobar'])).toEqual([]); expect( - ComposerReleases.parse([{ version: '1.2.3' }, { version: 'dev-main' }]) + ComposerReleasesArray.parse([ + { version: '1.2.3' }, + { version: 'dev-main' }, + ]) ).toEqual([{ version: '1.2.3' }, { version: 'dev-main' }]); }); }); + describe('ComposerReleasesRecord', () => { + it('rejects ComposerReleasesRecord', () => { + expect(() => ComposerReleasesRecord.parse(null)).toThrow(); + expect(() => ComposerReleasesRecord.parse(undefined)).toThrow(); + expect(() => ComposerReleasesRecord.parse('')).toThrow(); + expect(() => ComposerReleasesRecord.parse([])).toThrow(); + }); + + it('parses ComposerReleasesRecord', () => { + expect(ComposerReleasesRecord.parse({})).toEqual({}); + expect(ComposerReleasesRecord.parse({ foo: null })).toEqual({}); + expect(ComposerReleasesRecord.parse({ foo: 1, bar: 2, baz: 3 })).toEqual( + {} + ); + expect(ComposerReleasesRecord.parse({ foo: 'bar' })).toEqual({}); + expect( + ComposerReleasesRecord.parse({ + '0.0.1': { foo: 'bar' }, + '0.0.2': { version: '0.0.1' }, + '1.2.3': { version: '1.2.3' }, + 'dev-main': { version: 'dev-main' }, + }) + ).toEqual({ + '1.2.3': { version: '1.2.3' }, + 'dev-main': { version: 'dev-main' }, + }); + }); + }); + describe('parsePackageResponse', () => { it('parses package response', () => { expect(parsePackagesResponse('foo/bar', null)).toEqual([]); diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts index 4a5f379e12..5f26496fee 100644 --- a/lib/modules/datasource/packagist/schema.ts +++ b/lib/modules/datasource/packagist/schema.ts @@ -65,10 +65,23 @@ export const ComposerRelease = z ); export type ComposerRelease = z.infer<typeof ComposerRelease>; -export const ComposerReleases = z +export const ComposerReleasesArray = z .array(ComposerRelease.nullable().catch(null)) .transform((xs) => xs.filter((x): x is ComposerRelease => x !== null)); -export type ComposerReleases = z.infer<typeof ComposerReleases>; +export type ComposerReleasesArray = z.infer<typeof ComposerReleasesArray>; + +export const ComposerReleasesRecord = z + .record(ComposerRelease.nullable().catch(null)) + .transform((map) => { + const res: Record<string, ComposerRelease> = {}; + for (const [key, value] of Object.entries(map)) { + if (value !== null && value.version === key) { + res[key] = value; + } + } + return res; + }); +export type ComposerReleasesRecord = z.infer<typeof ComposerReleasesRecord>; export const ComposerPackagesResponse = z.object({ packages: z.record(z.unknown()), @@ -77,11 +90,11 @@ export const ComposerPackagesResponse = z.object({ export function parsePackagesResponse( packageName: string, packagesResponse: unknown -): ComposerReleases { +): ComposerReleasesArray { try { const { packages } = ComposerPackagesResponse.parse(packagesResponse); const array = MinifiedArray.parse(packages[packageName]); - const releases = ComposerReleases.parse(array); + const releases = ComposerReleasesArray.parse(array); return releases; } catch (err) { logger.debug( @@ -92,17 +105,15 @@ export function parsePackagesResponse( } } -export function parsePackagesResponses( - packageName: string, - packagesResponses: unknown[] +export function extractReleaseResult( + ...composerReleasesArrays: ComposerReleasesArray[] ): ReleaseResult | null { const releases: Release[] = []; let homepage: string | null | undefined; let sourceUrl: string | null | undefined; - for (const packagesResponse of packagesResponses) { - const releaseArray = parsePackagesResponse(packageName, packagesResponse); - for (const composerRelease of releaseArray) { + for (const composerReleasesArray of composerReleasesArrays) { + for (const composerRelease of composerReleasesArray) { const version = composerRelease.version.replace(/^v/, ''); const gitRef = composerRelease.version; @@ -144,3 +155,13 @@ export function parsePackagesResponses( return result; } + +export function parsePackagesResponses( + packageName: string, + packagesResponses: unknown[] +): ReleaseResult | null { + const releaseArrays = packagesResponses.map((pkgResp) => + parsePackagesResponse(packageName, pkgResp) + ); + return extractReleaseResult(...releaseArrays); +} diff --git a/lib/modules/datasource/packagist/types.ts b/lib/modules/datasource/packagist/types.ts index fea795612e..73948fe1df 100644 --- a/lib/modules/datasource/packagist/types.ts +++ b/lib/modules/datasource/packagist/types.ts @@ -32,5 +32,5 @@ export interface AllPackages { providersLazyUrl?: string; providerPackages: Record<string, string>; - includesPackages: Record<string, ReleaseResult>; + includesPackages: Record<string, ReleaseResult | null>; } -- GitLab