Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(packagist): Use schema for packages.json file #19930

Merged
merged 13 commits into from Jan 23, 2023
Merged
10 changes: 5 additions & 5 deletions lib/modules/datasource/packagist/index.spec.ts
Expand Up @@ -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' },
},
},
};
Expand Down Expand Up @@ -146,7 +146,7 @@ describe('modules/datasource/packagist/index', () => {
packages: [],
includes: {
'include/all$afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b.json': {
sha1: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b',
sha256: 'afbf74d51f31c7cbb5ff10304f9290bfb4f4e68b',
zharinov marked this conversation as resolved.
Show resolved Hide resolved
},
},
};
Expand Down
67 changes: 14 additions & 53 deletions 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';
Expand All @@ -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';
Expand All @@ -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,
rarkins marked this conversation as resolved.
Show resolved Hide resolved
};
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 {
Expand Down Expand Up @@ -186,7 +147,7 @@ export class PackagistDatasource extends Datasource {
}
}
const allPackages: AllPackages = {
packages,
packages: packages as never, // TODO: fix types (#9610)
providersUrl,
providersLazyUrl,
providerPackages,
Expand Down Expand Up @@ -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,
Expand Down
73 changes: 73 additions & 0 deletions lib/modules/datasource/packagist/schema.ts
Expand Up @@ -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>;
14 changes: 3 additions & 11 deletions 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;
Expand All @@ -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>;
}