diff --git a/lib/modules/datasource/packagist/index.spec.ts b/lib/modules/datasource/packagist/index.spec.ts index 4ef75442e11e33227c2a323606f98ed25188319f..935c7443173efe4acf269b492deb434658785fe5 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' }, }, }, }; @@ -146,7 +146,7 @@ describe('modules/datasource/packagist/index', () => { packages: [], includes: { 'include/all$afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b.json': { - sha1: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b', + sha256: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b', }, }, }; diff --git a/lib/modules/datasource/packagist/index.ts b/lib/modules/datasource/packagist/index.ts index 9171467dc48c1d6d4b9dcfc22a0014e58d7b20fc..a8a2219516765361dd5f651cb880fd247531bfcf 100644 --- a/lib/modules/datasource/packagist/index.ts +++ b/lib/modules/datasource/packagist/index.ts @@ -1,4 +1,5 @@ import URL from 'url'; +import is from '@sindresorhus/is'; import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import { cache } from '../../../util/cache/package/decorator'; @@ -11,13 +12,7 @@ import * as composerVersioning from '../../versioning/composer'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import * as schema from './schema'; -import type { - AllPackages, - PackageMeta, - PackagistFile, - RegistryFile, - RegistryMeta, -} from './types'; +import type { AllPackages, PackagistFile, RegistryFile } from './types'; export class PackagistDatasource extends Datasource { static readonly id = 'packagist'; @@ -41,46 +36,12 @@ export class PackagistDatasource extends Datasource { return username && password ? { username, password } : {}; } - private async getRegistryMeta(regUrl: string): Promise<RegistryMeta | null> { + private async getRegistryMeta(regUrl: string): Promise<schema.RegistryMeta> { const url = URL.resolve(ensureTrailingSlash(regUrl), 'packages.json'); const opts = PackagistDatasource.getHostOpts(url); - const res = (await this.http.getJson<PackageMeta>(url, opts)).body; - const meta: RegistryMeta = { - providerPackages: {}, - packages: res.packages, - }; - if (res.includes) { - meta.includesFiles = []; - for (const [name, val] of Object.entries(res.includes)) { - const file = { - key: name.replace(val.sha256, '%hash%'), - sha256: val.sha256, - }; - meta.includesFiles.push(file); - } - } - if (res['providers-url']) { - meta.providersUrl = res['providers-url']; - } - if (res['providers-lazy-url']) { - meta.providersLazyUrl = res['providers-lazy-url']; - } - if (res['provider-includes']) { - meta.files = []; - for (const [key, val] of Object.entries(res['provider-includes'])) { - const file = { - key, - sha256: val.sha256, - }; - meta.files.push(file); - } - } - if (res.providers) { - for (const [key, val] of Object.entries(res.providers)) { - meta.providerPackages[key] = val.sha256; - } - } - return meta; + const { body } = await this.http.getJson(url, opts); + const res = schema.RegistryMeta.parse(body); + return res; } private static isPrivatePackage(regUrl: string): boolean { @@ -186,7 +147,7 @@ export class PackagistDatasource extends Datasource { } } const allPackages: AllPackages = { - packages, + packages: packages as never, // TODO: fix types (#9610) providersUrl, providersLazyUrl, providerPackages, @@ -248,13 +209,13 @@ export class PackagistDatasource extends Datasource { return includesPackages[packageName]; } let pkgUrl: string; - if (packageName in providerPackages) { - pkgUrl = URL.resolve( - registryUrl, - providersUrl! - .replace('%package%', packageName) - .replace('%hash%', providerPackages[packageName]) - ); + const hash = providerPackages[packageName]; + if (providersUrl && !is.undefined(hash)) { + let url = providersUrl.replace('%package%', packageName); + if (hash) { + url = url.replace('%hash%', hash); + } + pkgUrl = URL.resolve(registryUrl, url); } else if (providersLazyUrl) { pkgUrl = URL.resolve( registryUrl, diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts index 4a5f379e128eb68e64f68daf83a746eb4cd19feb..2ed4e70e9221c64df5d03dc27c22abe390f5d021 100644 --- a/lib/modules/datasource/packagist/schema.ts +++ b/lib/modules/datasource/packagist/schema.ts @@ -144,3 +144,76 @@ export function parsePackagesResponses( return result; } + +const RegistryFile = z.object({ + key: z.string(), + sha256: z.string(), +}); +export type RegistryFile = z.infer<typeof RegistryFile>; + +const RegistryMetaFile = z.object({ + sha256: z.string().nullable(), +}); +type RegistryMetaFile = z.infer<typeof RegistryMetaFile>; + +const RegistryMetaFiles = z + .record(RegistryMetaFile.nullable().catch(null)) + .transform((obj) => { + // Remove all null values + // TODO: extract as schema utility + const result: Record<string, RegistryMetaFile> = {}; + for (const [key, val] of Object.entries(obj)) { + if (val !== null) { + result[key] = val; + } + } + return result; + }); + +const RegistryMetaIncludes = RegistryMetaFiles.transform( + (obj): RegistryFile[] => { + const result: RegistryFile[] = []; + for (const [key, { sha256 }] of Object.entries(obj)) { + if (sha256) { + result.push({ key, sha256 }); + } + } + return result; + } +) + .nullable() + .catch(null); + +export const RegistryMeta = z + .object({ + ['packages']: z.record(z.record(ComposerRelease)).nullable().catch(null), + ['includes']: RegistryMetaIncludes, + ['provider-includes']: RegistryMetaIncludes, + ['providers-url']: z.string().nullable().catch(null), + ['providers-lazy-url']: z.string().optional().nullable().catch(null), + ['providers']: RegistryMetaFiles.transform((obj) => { + const result: Record<string, string | null> = {}; + for (const [key, { sha256 }] of Object.entries(obj)) { + result[key] = sha256; + } + return result; + }).catch({}), + }) + .transform( + ({ + ['packages']: packages, + ['includes']: includesFiles, + ['providers']: providerPackages, + ['provider-includes']: files, + ['providers-url']: providersUrl, + ['providers-lazy-url']: providersLazyUrl, + }) => ({ + packages, + includesFiles, + providerPackages, + files, + providersUrl, + providersLazyUrl, + }) + ); +export type RegistryMeta = z.infer<typeof RegistryMeta>; diff --git a/lib/modules/datasource/packagist/types.ts b/lib/modules/datasource/packagist/types.ts index fea795612e7fc3ff316c1a3fcc1576cb1026c8b8..317c1db50b81f7663a796e07cab4001813678822 100644 --- a/lib/modules/datasource/packagist/types.ts +++ b/lib/modules/datasource/packagist/types.ts @@ -1,13 +1,5 @@ import type { ReleaseResult } from '../types'; -export interface PackageMeta { - includes?: Record<string, { sha256: string }>; - packages: Record<string, RegistryFile>; - 'provider-includes': Record<string, { sha256: string }>; - providers: Record<string, { sha256: string }>; - 'providers-lazy-url'?: string; - 'providers-url'?: string; -} export interface RegistryFile { key: string; sha256: string; @@ -28,9 +20,9 @@ export interface PackagistFile { export interface AllPackages { packages: Record<string, RegistryFile>; - providersUrl?: string; - providersLazyUrl?: string; - providerPackages: Record<string, string>; + providersUrl: string | null; + providersLazyUrl: string | null; + providerPackages: Record<string, string | null>; includesPackages: Record<string, ReleaseResult>; }