import { isNullOrUndefined } from '@sindresorhus/is'; import { logger } from '../../../logger/index.ts'; import { getExpression } from '../../../util/jsonata.ts'; import { Datasource } from '../datasource.ts'; import type { DigestConfig, GetReleasesConfig, ReleaseResult, } from '../types.ts'; import { fetchers } from './formats/index.ts'; import { ReleaseResultZod } from './schema.ts'; import { getCustomConfig } from './utils.ts'; export class CustomDatasource extends Datasource { static readonly id = 'custom'; override customRegistrySupport = true; constructor() { super(CustomDatasource.id); } async getReleases( getReleasesConfig: GetReleasesConfig, ): Promise { const config = getCustomConfig(getReleasesConfig); if (isNullOrUndefined(config)) { return null; } const { defaultRegistryUrlTemplate, transformTemplates, format } = config; const fetcher = fetchers[format]; const isLocalRegistry = defaultRegistryUrlTemplate.startsWith('file://'); let data: unknown; try { if (isLocalRegistry) { data = await fetcher.readFile( defaultRegistryUrlTemplate.replace('file://', ''), ); } else { data = await fetcher.fetch(this.http, defaultRegistryUrlTemplate); } } catch (e) { this.handleHttpErrors(e); return null; } logger.trace( { data }, `Custom datasource API fetcher '${format}' received data. Starting transformation.`, ); for (const transformTemplate of transformTemplates) { const expression = getExpression(transformTemplate); if (expression instanceof Error) { logger.once.warn( { errorMessage: expression.message, transformTemplate }, 'Invalid JSONata expression', ); return null; } try { const modifiedData = await expression.evaluate(data); logger.trace( { before: data, after: modifiedData }, `Custom datasource transformed data.`, ); data = modifiedData; } catch (err) { logger.once.warn( { err, transformTemplate }, 'Error while evaluating JSONata expression', ); return null; } } try { const parsed = ReleaseResultZod.parse(data); return structuredClone(parsed); } catch (err) { logger.debug({ err }, `Response has failed validation`); logger.trace({ data }, 'Response that has failed validation'); return null; } } override getDigest( _cfg: DigestConfig, _newValue?: string, ): Promise { // Return null here to support setting a digest: value can be provided digest in getReleases return Promise.resolve(null); } }