Files
renovate/lib/modules/datasource/orb/index.ts
Pete Steyert-Woods 2fc1b5ce67 feat(datasource/orb): use REST API instead of graphql-unstable (#45205)
* refactor(datasource/orb): use REST API instead of graphql-unstable

Switch the orb datasource from the `graphql-unstable` GraphQL endpoint to
the stable CircleCI REST API (`GET /api/v3/orb/packages?filter[name]=`).

The single REST call returns the same fields the datasource needs
(`is_private`, `home_url`, and the nested `orb_versions` with
`created_at`), so behaviour is unchanged for public and private orbs on
circleci.com. Missing orbs are now detected via an empty `data` array
rather than a null `orb` field.

* convert to zod schema

* refactor(datasource/orb): use nullish helper and schema-side transform

Address review feedback:
- use the Nullish schema helper for optional attributes
- catch([]) so orb_versions is never null/undefined
- move the data transform (isPrivate, releases) into the schema; the
  homepage fallback stays in the datasource since it needs the requested
  packageName

* refactor(datasource/orb): drop snapshot and use toEqual assertion

* refactor(datasource/orb): change assertion to deepnullish

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>

---------

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
2026-09-03 07:19:11 +00:00

74 lines
2.2 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import { withCache } from '../../../util/cache/package/with-cache.ts';
import { getQueryString, joinUrlParts } from '../../../util/url.ts';
import { Datasource } from '../datasource.ts';
import type { GetReleasesConfig, ReleaseResult } from '../types.ts';
import { OrbPackagesResponse } from './schema.ts';
export class OrbDatasource extends Datasource {
static readonly id = 'orb';
constructor() {
super(OrbDatasource.id);
}
override readonly customRegistrySupport = true;
override readonly defaultRegistryUrls = ['https://circleci.com/'];
override readonly registryStrategy = 'hunt';
override readonly releaseTimestampSupport = true;
override readonly releaseTimestampNote =
'The release timestamp is determined from the `created_at` field in the results.';
private async _getReleases({
packageName,
registryUrl,
}: GetReleasesConfig): Promise<ReleaseResult | null> {
/* v8 ignore next -- should never happen */
if (!registryUrl) {
return null;
}
const url = `${joinUrlParts(
registryUrl,
'api/v3/orb/packages',
)}?${getQueryString({ 'filter[name]': packageName })}`;
try {
const { body } = await this.http.getJson(url, OrbPackagesResponse);
const pkg = body.data[0];
if (!pkg) {
logger.debug({ packageName }, `Failed to look up orb ${packageName}`);
return null;
}
// The homepage fallback uses the requested packageName, which the schema
// has no access to, so it is built here rather than in the transform.
const homepage = pkg.homeUrl?.length
? pkg.homeUrl
: `https://circleci.com/developer/orbs/orb/${packageName}`;
const dep = {
homepage,
isPrivate: pkg.isPrivate,
releases: pkg.releases,
};
logger.trace({ dep }, 'dep');
return dep;
} catch (err) {
this.handleGenericErrors(err);
}
}
override getReleases(
config: GetReleasesConfig,
): Promise<ReleaseResult | null> {
return withCache(
{
namespace: `datasource-${OrbDatasource.id}`,
key: `${config.registryUrl}:${config.packageName}`,
fallback: true,
},
() => this._getReleases(config),
);
}
}