diff --git a/lib/modules/datasource/conan/common.ts b/lib/modules/datasource/conan/common.ts index bba6a0a0213af2ba3d3d885b7134d3008eb184d8..fce9a20255a83ac68eaa08bfc5d92608923dcfcd 100644 --- a/lib/modules/datasource/conan/common.ts +++ b/lib/modules/datasource/conan/common.ts @@ -6,7 +6,7 @@ export const defaultRegistryUrl = 'https://center2.conan.io/'; export const datasource = 'conan'; export const conanDatasourceRegex = regEx( - /(?<name>[a-zA-Z\-_0-9]+)\/(?<version>[^@/\n]+)(?<userChannel>@\S+\/\S+)/gim, + /^(?<name>[a-zA-Z\-_0-9]+)\/(?<version>[^@/\n]+)(?<userChannel>@\S+\/\S+)$/im, ); export function getConanPackage(packageName: string): ConanPackage { diff --git a/lib/modules/datasource/conan/index.spec.ts b/lib/modules/datasource/conan/index.spec.ts index f12ea65847e87899acabb395f6aa9909310172e3..56c7faa5c1db71253be5b9f92c973764a3d1ecb5 100644 --- a/lib/modules/datasource/conan/index.spec.ts +++ b/lib/modules/datasource/conan/index.spec.ts @@ -360,6 +360,7 @@ describe('modules/datasource/conan/index', () => { version: '1.1.1', }, ], + sourceUrl: 'https://fake.conan.url.com', }); }); diff --git a/lib/modules/datasource/conan/index.ts b/lib/modules/datasource/conan/index.ts index 8ecb40124792bb9bcbce8a8358179b06962f4b82..b944a97dc633be199c57442208633a4b6112490f 100644 --- a/lib/modules/datasource/conan/index.ts +++ b/lib/modules/datasource/conan/index.ts @@ -3,7 +3,6 @@ import { logger } from '../../../logger'; import { cache } from '../../../util/cache/package/decorator'; import { GithubHttp } from '../../../util/http/github'; import { ensureTrailingSlash, joinUrlParts } from '../../../util/url'; -import { parseSingleYaml } from '../../../util/yaml'; import * as allVersioning from '../../versioning'; import { Datasource } from '../datasource'; import type { @@ -13,19 +12,14 @@ import type { ReleaseResult, } from '../types'; import { isArtifactoryServer } from '../util'; +import { datasource, defaultRegistryUrl, getConanPackage } from './common'; import { - conanDatasourceRegex, - datasource, - defaultRegistryUrl, - getConanPackage, -} from './common'; -import type { + ConanCenterReleases, ConanJSON, + ConanLatestRevision, ConanProperties, ConanRevisionJSON, - ConanRevisionsJSON, - ConanYAML, -} from './types'; +} from './schema'; export class ConanDatasource extends Datasource { static readonly id = datasource; @@ -62,13 +56,7 @@ export class ConanDatasource extends Datasource { const res = await this.githubHttp.get(url, { headers: { accept: 'application/vnd.github.v3.raw' }, }); - // TODO: use schema (#9610) - const doc = parseSingleYaml<ConanYAML>(res.body); - return { - releases: Object.keys(doc?.versions ?? {}).map((version) => ({ - version, - })), - }; + return ConanCenterReleases.parse(res.body); } @cache({ @@ -94,10 +82,11 @@ export class ConanDatasource extends Datasource { conanPackage.userAndChannel, '/revisions', ); - const revisionRep = - await this.http.getJson<ConanRevisionsJSON>(revisionLookUp); - const revisions = revisionRep?.body.revisions; - return revisions?.[0].revision ?? null; + const { body: digest } = await this.http.getJson( + revisionLookUp, + ConanLatestRevision, + ); + return digest; } @cache({ @@ -135,25 +124,16 @@ export class ConanDatasource extends Datasource { ); try { - const rep = await this.http.getJson<ConanJSON>(lookupUrl); - const versions = rep?.body; - if (versions) { + const rep = await this.http.getJson(lookupUrl); + const conanJson = ConanJSON.parse(rep.body); + if (conanJson) { logger.trace({ lookupUrl }, 'Got conan api result'); const dep: ReleaseResult = { releases: [] }; - for (const resultString of Object.values(versions.results ?? {})) { - conanDatasourceRegex.lastIndex = 0; - const fromMatch = conanDatasourceRegex.exec(resultString); - if (fromMatch?.groups?.version && fromMatch?.groups?.userChannel) { - const version = fromMatch.groups.version; - if (fromMatch.groups.userChannel === userAndChannel) { - const result: Release = { - version, - }; - dep.releases.push(result); - } - } - } + const conanJsonReleases: Release[] = conanJson + .filter(({ userChannel }) => userChannel === userAndChannel) + .map(({ version }) => ({ version })); + dep.releases.push(...conanJsonReleases); try { if (isArtifactoryServer(rep)) { @@ -182,25 +162,22 @@ export class ConanDatasource extends Datasource { url, `v2/conans/${conanPackage.conanName}/${latestVersion}/${conanPackage.userAndChannel}/latest`, ); - const revResp = - await this.http.getJson<ConanRevisionJSON>(latestRevisionUrl); - const packageRev = revResp.body.revision; + const { + body: { revision: packageRev }, + } = await this.http.getJson(latestRevisionUrl, ConanRevisionJSON); const [user, channel] = conanPackage.userAndChannel.split('/'); const packageUrl = joinUrlParts( `${groups.host}/artifactory/api/storage/${groups.repo}`, `${user}/${conanPackage.conanName}/${latestVersion}/${channel}/${packageRev}/export/conanfile.py?properties=conan.package.url`, ); - const packageUrlResp = - await this.http.getJson<ConanProperties>(packageUrl); - - if ( - packageUrlResp.body.properties && - 'conan.package.url' in packageUrlResp.body.properties - ) { - const conanPackageUrl = - packageUrlResp.body.properties['conan.package.url'][0]; - dep.sourceUrl = conanPackageUrl; + const { body: conanProperties } = await this.http.getJson( + packageUrl, + ConanProperties, + ); + const { sourceUrl } = conanProperties; + if (sourceUrl) { + dep.sourceUrl = sourceUrl; } } } catch (err) { diff --git a/lib/modules/datasource/conan/schema.ts b/lib/modules/datasource/conan/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ca42c64f9fd97e7dccfcd11048e3f6fae2a2584 --- /dev/null +++ b/lib/modules/datasource/conan/schema.ts @@ -0,0 +1,66 @@ +import { z } from 'zod'; +import { LooseArray, Yaml } from '../../../util/schema-utils'; +import type { ReleaseResult } from '../types'; +import { conanDatasourceRegex } from './common'; + +export const ConanCenterReleases = Yaml.pipe( + z.object({ + versions: z.record(z.string(), z.unknown()), + }), +) + .transform( + ({ versions }): ReleaseResult => ({ + releases: Object.keys(versions).map((version) => ({ version })), + }), + ) + .nullable() + .catch(null); + +export const ConanJSON = z + .object({ + results: z + .string() + .array() + .transform((array) => + array.map((val) => val.match(conanDatasourceRegex)?.groups), + ) + .pipe( + LooseArray( + z.object({ + name: z.string(), + version: z.string(), + userChannel: z.string(), + }), + ), + ), + }) + .transform(({ results }) => results) + .nullable() + .catch(null); + +export const ConanRevisionJSON = z.object({ + revision: z.string(), + time: z.string(), +}); + +export const ConanLatestRevision = z + .object({ revisions: z.unknown().array() }) + .transform(({ revisions }) => revisions[0]) + .pipe(ConanRevisionJSON) + .transform(({ revision }) => revision) + .nullable() + .catch(null); + +export const ConanProperties = z + .object({ + properties: z.object({ + 'conan.package.url': z.union([ + z.string().transform((url) => [url]), + z.string().array(), + ]), + }), + }) + .transform(({ properties }) => { + const sourceUrl = properties['conan.package.url'][0]; + return { sourceUrl }; + }); diff --git a/lib/modules/datasource/conan/types.ts b/lib/modules/datasource/conan/types.ts index bb65481f5015f8665d2ddc3075d10449ac0da2da..18dda2e72854d1efdcb821e227ee358c10acffed 100644 --- a/lib/modules/datasource/conan/types.ts +++ b/lib/modules/datasource/conan/types.ts @@ -1,35 +1,4 @@ -export interface ConanJSON { - results?: Record<string, string>; -} - -export interface ConanRevisionJSON { - revision: string; - time: string; -} - -export interface ConanRevisionsJSON { - revisions?: Record<string, ConanRevisionJSON>; -} - -export interface ConanYAML { - versions?: Record<string, unknown>; -} - export interface ConanPackage { conanName: string; userAndChannel: string; } - -export interface ConanRecipeProperties { - 'conan.package.channel': string[]; - 'conan.package.license': string[]; - 'conan.package.name': string[]; - 'conan.package.url': string[]; - 'conan.package.user': string[]; - 'conan.package.version': string[]; -} - -export interface ConanProperties { - properties: ConanRecipeProperties; - uri: string; -}