diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts index 43024bbb3bf3e10b5340d060f85b17a712110185..f1d15e2ddee08fd1a01e7622e5caf0721cf3cd11 100644 --- a/lib/modules/datasource/packagist/index.ts +++ b/lib/modules/datasource/packagist/index.ts @@ -10,6 +10,7 @@ import * as composerVersioning from '../../versioning/composer'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import * as schema from './schema'; +import { extractDepReleases } from './schema'; import type { AllPackages, PackageMeta, @@ -118,30 +119,6 @@ export class PackagistDatasource extends Datasource { return packagistFile; } - /* 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)); - } - - 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({ namespace: `datasource-${PackagistDatasource.id}`, key: (regUrl: string) => regUrl, @@ -179,7 +156,7 @@ export class PackagistDatasource extends Datasource { tasks.push(async () => { const res = await this.getPackagistFile(regUrl, file); for (const [key, val] of Object.entries(res.packages ?? {})) { - includesPackages[key] = PackagistDatasource.extractDepReleases(val); + includesPackages[key] = extractDepReleases(val); } }); } @@ -240,9 +217,7 @@ export class PackagistDatasource extends Datasource { includesPackages, } = allPackages; if (packages?.[packageName]) { - const dep = PackagistDatasource.extractDepReleases( - packages[packageName] - ); + const dep = extractDepReleases(packages[packageName]); return dep; } if (includesPackages?.[packageName]) { @@ -268,7 +243,7 @@ export class PackagistDatasource extends Datasource { // TODO: fix types (#9610) const versions = (await this.http.getJson<any>(pkgUrl, opts)).body .packages[packageName]; - const dep = PackagistDatasource.extractDepReleases(versions); + const dep = extractDepReleases(versions); logger.trace({ dep }, 'dep'); return dep; } catch (err) /* istanbul ignore next */ { diff --git a/lib/modules/datasource/packagist/schema.spec.ts b/lib/modules/datasource/packagist/schema.spec.ts index 0180a1ab9c4b69f1cb906488c83579b8cfc86dfc..9fde962fcc2d5fc9fa4cec1c314cea2a584391a0 100644 --- a/lib/modules/datasource/packagist/schema.spec.ts +++ b/lib/modules/datasource/packagist/schema.spec.ts @@ -1,8 +1,7 @@ import type { ReleaseResult } from '../types'; import { ComposerRelease, - ComposerReleasesArray, - ComposerReleasesRecord, + ComposerReleases, MinifiedArray, parsePackagesResponse, parsePackagesResponses, @@ -111,57 +110,22 @@ describe('modules/datasource/packagist/schema', () => { }); }); - 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 ComposerReleasesArray', () => { - expect(ComposerReleasesArray.parse([])).toEqual([]); - expect(ComposerReleasesArray.parse([null])).toEqual([]); - expect(ComposerReleasesArray.parse([1, 2, 3])).toEqual([]); - expect(ComposerReleasesArray.parse(['foobar'])).toEqual([]); + describe('ComposerReleases', () => { + it('parses ComposerReleases', () => { + expect(ComposerReleases.parse(null)).toBeEmptyArray(); + expect(ComposerReleases.parse(undefined)).toBeEmptyArray(); + expect(ComposerReleases.parse('')).toBeEmptyArray(); + expect(ComposerReleases.parse({})).toBeEmptyArray(); + expect(ComposerReleases.parse([])).toBeEmptyArray(); + expect(ComposerReleases.parse([null])).toBeEmptyArray(); + expect(ComposerReleases.parse([1, 2, 3])).toBeEmptyArray(); + expect(ComposerReleases.parse(['foobar'])).toBeEmptyArray(); expect( - ComposerReleasesArray.parse([ - { version: '1.2.3' }, - { version: 'dev-main' }, - ]) + ComposerReleases.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 5f26496fee505f4bc0508df2861318154f7535b8..9e15d5bf157edb1a94aad1d7806c94276b75d49e 100644 --- a/lib/modules/datasource/packagist/schema.ts +++ b/lib/modules/datasource/packagist/schema.ts @@ -65,23 +65,16 @@ export const ComposerRelease = z ); export type ComposerRelease = z.infer<typeof ComposerRelease>; -export const ComposerReleasesArray = z - .array(ComposerRelease.nullable().catch(null)) +export const ComposerReleases = z + .union([ + z.array(ComposerRelease.nullable().catch(null)), + z + .record(ComposerRelease.nullable().catch(null)) + .transform((map) => Object.values(map)), + ]) + .catch([]) .transform((xs) => xs.filter((x): x is ComposerRelease => x !== null)); -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 type ComposerReleases = z.infer<typeof ComposerReleases>; export const ComposerPackagesResponse = z.object({ packages: z.record(z.unknown()), @@ -90,11 +83,11 @@ export const ComposerPackagesResponse = z.object({ export function parsePackagesResponse( packageName: string, packagesResponse: unknown -): ComposerReleasesArray { +): ComposerReleases { try { const { packages } = ComposerPackagesResponse.parse(packagesResponse); const array = MinifiedArray.parse(packages[packageName]); - const releases = ComposerReleasesArray.parse(array); + const releases = ComposerReleases.parse(array); return releases; } catch (err) { logger.debug( @@ -106,7 +99,7 @@ export function parsePackagesResponse( } export function extractReleaseResult( - ...composerReleasesArrays: ComposerReleasesArray[] + ...composerReleasesArrays: ComposerReleases[] ): ReleaseResult | null { const releases: Release[] = []; let homepage: string | null | undefined; @@ -156,6 +149,13 @@ export function extractReleaseResult( return result; } +export function extractDepReleases( + composerReleases: unknown +): ReleaseResult | null { + const parsedReleases = ComposerReleases.parse(composerReleases); + return extractReleaseResult(parsedReleases); +} + export function parsePackagesResponses( packageName: string, packagesResponses: unknown[]