mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 02:00:10 +00:00
0d9888a4f2
* feat(datasource): add GraalVM version datasource with support for JDK and JRE releases * feat(datasource): add GraalVM version datasource with support for JDK and JRE releases * feat(datasource): add GraalVM version datasource with support for JDK and JRE releases * feat(datasource): add GraalVM version datasource with support for JDK and JRE releases * feat(datasource): add GraalVM version datasource with support for JDK and JRE releases * add coverage * fix * fix * fix * code cov * code cov * code cov * refactor(datasource): integrate GraalVM into java-version datasource Merges graalvm-version datasource into java-version as a unified multi-vendor Java distribution datasource. Implements vendor-based modular architecture with routing layer for Adoptium (Eclipse Temurin) and Oracle GraalVM. Architecture: - Vendor-specific handlers (adoptium.ts, graalvm.ts) with routing in index.ts - Vendor-aware system detection with different arch/os mappings per vendor - Custom registry support for GraalVM's mise-java API - Adoptium remains the default registry for backwards compatibility Package names: - New explicit: adoptium-jdk, adoptium-jre, oracle-graalvm-jdk, oracle-graalvm-jre - Legacy (map to Adoptium): java, java-jdk, java-jre Changes: - Created adoptium.ts and graalvm.ts vendor handlers - Enhanced common.ts with vendor detection and vendor-specific system mappings - Updated index.ts with routing layer and custom registry support - Merged test suites with comprehensive coverage for both vendors - Updated mise manager integration to use JavaVersionDatasource - Removed graalvm-version datasource directory All 65 datasource tests and 93 mise manager tests passing. * fix * test(datasource): add coverage for non-HttpError paths in java-version * fix(datasource): remove dead code and add coverage for java-version Remove unreachable HttpError handling from _getReleases catch block since both getAdoptiumReleases and getGraalvmReleases handle HttpError internally. Add test for the graalvmRegistryUrl fallback when registryUrl is undefined. * fix(datasource): address review comments for GraalVM datasource - Remove unused 'datasource-graalvm-version' cache namespace - Convert MiseJavaRelease interface to Zod schema and derive type from it - Use http.getJson() with the Zod schema in getGraalvmReleases instead of getJsonUnchecked, letting the schema handle invalid/empty responses - Update getGraalvmReleases test mock to use getJson * docs: remove unused datasource-graalvm-version from cacheTtlOverride docs * fix(datasource): preserve Adoptium config typing * fix(datasource): harden GraalVM release parsing * fix(datasource): address GraalVM review feedback * test: cover GraalVM mise package mapping * test(datasource): cover default GraalVM registry fallback * test(datasource): cover legacy registry resolution * test(datasource): type legacy datasource mocks * fix(datasource): harden GraalVM defaults and responses * refactor(datasource): use function-only registry accessors * test(datasource): cover registry accessor functions * test: restore coverage exclusions * chore: retrigger CI checks
642 lines
19 KiB
TypeScript
642 lines
19 KiB
TypeScript
import { isBoolean } from '@sindresorhus/is';
|
|
import upath from 'upath';
|
|
import { GlobalConfig } from '../../../config/global.ts';
|
|
import { logger } from '../../../logger/index.ts';
|
|
import * as memCache from '../../../util/cache/memory/index.ts';
|
|
import * as packageCache from '../../../util/cache/package/index.ts';
|
|
import { withCache } from '../../../util/cache/package/with-cache.ts';
|
|
import { privateCacheDir, readCacheFile } from '../../../util/fs/index.ts';
|
|
import { createSimpleGit } from '../../../util/git/index.ts';
|
|
import { toSha256 } from '../../../util/hash.ts';
|
|
import { memCacheProvider } from '../../../util/http/cache/memory-http-cache-provider.ts';
|
|
import { HttpError } from '../../../util/http/index.ts';
|
|
import { acquireLock } from '../../../util/mutex.ts';
|
|
import { newlineRegex, regEx } from '../../../util/regex.ts';
|
|
import { Json } from '../../../util/schema-utils/index.ts';
|
|
import { asTimestamp } from '../../../util/timestamp.ts';
|
|
import { joinUrlParts, parseUrl } from '../../../util/url.ts';
|
|
import * as cargoVersioning from '../../versioning/cargo/index.ts';
|
|
import { Datasource } from '../datasource.ts';
|
|
import type {
|
|
GetReleasesConfig,
|
|
PostprocessReleaseConfig,
|
|
PostprocessReleaseResult,
|
|
Release,
|
|
ReleaseResult,
|
|
} from '../types.ts';
|
|
import type { CrateMetadata } from './schema.ts';
|
|
import {
|
|
CrateMetadataResponse,
|
|
RegistryConfig,
|
|
ReleaseTimestamp,
|
|
} from './schema.ts';
|
|
import type { CrateRecord, RegistryFlavor, RegistryInfo } from './types.ts';
|
|
|
|
type CloneResult =
|
|
| {
|
|
err: Error;
|
|
clonePath?: undefined;
|
|
}
|
|
| {
|
|
clonePath: string;
|
|
err?: undefined;
|
|
};
|
|
|
|
export class CrateDatasource extends Datasource {
|
|
static readonly id = 'crate';
|
|
|
|
constructor() {
|
|
super(CrateDatasource.id);
|
|
}
|
|
|
|
override getDefaultRegistryUrls(_packageName: string): string[] {
|
|
return ['sparse+https://index.crates.io/'];
|
|
}
|
|
|
|
override defaultVersioning = cargoVersioning.id;
|
|
|
|
override readonly sourceUrlSupport = 'package';
|
|
override readonly sourceUrlNote =
|
|
'The source URL is determined from the `repository` field in the results.';
|
|
|
|
override readonly releaseTimestampSupport = true;
|
|
override readonly releaseTimestampNote =
|
|
'The release timestamp is determined from `pubtime` field from crates.io index if available, or `version.created_at` field from crates.io API otherwise.';
|
|
|
|
private async _getReleases({
|
|
packageName,
|
|
registryUrl,
|
|
}: GetReleasesConfig): Promise<ReleaseResult | null> {
|
|
/* v8 ignore if -- should never happen */
|
|
if (!registryUrl) {
|
|
logger.warn(
|
|
'crate datasource: No registryUrl specified, cannot perform getReleases',
|
|
);
|
|
return null;
|
|
}
|
|
|
|
const registryInfo = await CrateDatasource.fetchRegistryInfo({
|
|
packageName,
|
|
registryUrl,
|
|
});
|
|
if (!registryInfo) {
|
|
logger.debug(`Could not fetch registry info from ${registryUrl}`);
|
|
return null;
|
|
}
|
|
|
|
const dependencyUrl = CrateDatasource.getDependencyUrl(
|
|
registryInfo,
|
|
packageName,
|
|
);
|
|
|
|
const payload = await this.fetchCrateRecordsPayload(
|
|
registryInfo,
|
|
packageName,
|
|
);
|
|
const lines = payload
|
|
.split(newlineRegex) // break into lines
|
|
.map((line) => line.trim()) // remove whitespace
|
|
.filter((line) => line.length !== 0) // remove empty lines
|
|
.map((line) => JSON.parse(line) as CrateRecord); // parse
|
|
|
|
const metadata = await this.getCrateMetadata(registryInfo, packageName);
|
|
|
|
const result: ReleaseResult = {
|
|
dependencyUrl,
|
|
releases: [],
|
|
};
|
|
|
|
if (metadata?.homepage) {
|
|
result.homepage = metadata.homepage;
|
|
}
|
|
|
|
if (metadata?.repository) {
|
|
result.sourceUrl = metadata.repository;
|
|
}
|
|
|
|
result.releases = lines
|
|
.map((line) => {
|
|
const versionOrig = line.vers;
|
|
const version = versionOrig.replace(regEx(/\+.*$/), '');
|
|
const release: Release = { version };
|
|
|
|
if (versionOrig !== version) {
|
|
release.versionOrig = versionOrig;
|
|
}
|
|
|
|
if (line.yanked) {
|
|
release.isDeprecated = true;
|
|
}
|
|
|
|
if (line.rust_version) {
|
|
release.constraints = { rust: [line.rust_version] };
|
|
}
|
|
|
|
if (line.pubtime) {
|
|
release.releaseTimestamp = asTimestamp(line.pubtime);
|
|
}
|
|
|
|
return release;
|
|
})
|
|
.filter((release) => release.version);
|
|
if (!result.releases.length) {
|
|
return null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
getReleases(config: GetReleasesConfig): Promise<ReleaseResult | null> {
|
|
return withCache(
|
|
{
|
|
namespace: `datasource-${CrateDatasource.id}`,
|
|
// TODO: types (#22198)
|
|
key: `${config.registryUrl}/${config.packageName}`,
|
|
cacheable: CrateDatasource.isCratesIo(config.registryUrl),
|
|
fallback: true,
|
|
},
|
|
() => this._getReleases(config),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Fetches the registry's config.json which provides the API base URL
|
|
* and download URL template.
|
|
* See: https://doc.rust-lang.org/cargo/reference/registry-index.html#index-configuration
|
|
*/
|
|
private async fetchRegistryConfig(
|
|
info: RegistryInfo,
|
|
): Promise<RegistryConfig | null> {
|
|
const cacheKey = `crate-datasource/registry-config/${info.rawUrl}`;
|
|
const cached = memCache.get<RegistryConfig>(cacheKey);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
if (info.clonePath) {
|
|
try {
|
|
const configPath = upath.join(info.clonePath, 'config.json');
|
|
const content = await readCacheFile(configPath, 'utf8');
|
|
const parsed = Json.pipe(RegistryConfig).parse(content);
|
|
memCache.set(cacheKey, parsed);
|
|
return parsed;
|
|
} catch {
|
|
logger.debug(
|
|
{ registryUrl: info.rawUrl },
|
|
'Could not read config.json from cloned registry',
|
|
);
|
|
}
|
|
} else {
|
|
try {
|
|
const configUrl = joinUrlParts(info.rawUrl, 'config.json');
|
|
const { body } = await this.http.getJson(configUrl, RegistryConfig);
|
|
memCache.set(cacheKey, body);
|
|
return body;
|
|
} catch {
|
|
logger.debug(
|
|
{ registryUrl: info.rawUrl },
|
|
'Could not fetch registry config.json',
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* The registry web API specification only defines publish, yank, unyank,
|
|
* owners, search and login endpoints, see
|
|
* https://doc.rust-lang.org/cargo/reference/registry-web-api.html.
|
|
* `GET api/v1/crates/<name>` and `GET api/v1/crates/<name>/<version>` are
|
|
* crates.io extensions. Registries which mirror the crates.io index keep
|
|
* `api` pointing at crates.io and are used without probing. Any other
|
|
* registry is probed once and remembered as unsupported when it answers
|
|
* 404, since private registries (Artifactory, CodeArtifact, ...) advertise
|
|
* an `api` URL for publishing only.
|
|
*/
|
|
private static isCratesIoApi(api: string): boolean {
|
|
return parseUrl(api)?.hostname === 'crates.io';
|
|
}
|
|
|
|
private static isNotFound(err: unknown): boolean {
|
|
return err instanceof HttpError && err.response?.statusCode === 404;
|
|
}
|
|
|
|
/**
|
|
* The marker is keyed by the `api` URL, since the missing endpoints are a
|
|
* property of the API server, not of the index that points to it.
|
|
*/
|
|
private async isReadApiUnsupported(api: string): Promise<boolean> {
|
|
if (CrateDatasource.isCratesIoApi(api)) {
|
|
return false;
|
|
}
|
|
|
|
const memKey = `crate-datasource/registry-api-unsupported/${api}`;
|
|
const cached = memCache.get<boolean>(memKey);
|
|
if (isBoolean(cached)) {
|
|
return cached;
|
|
}
|
|
|
|
const persisted = await packageCache.get<boolean>(
|
|
'datasource-crate-registry-api',
|
|
api,
|
|
);
|
|
const unsupported = persisted === true;
|
|
memCache.set(memKey, unsupported);
|
|
return unsupported;
|
|
}
|
|
|
|
private async markReadApiUnsupported(
|
|
registryUrl: string,
|
|
api: string,
|
|
): Promise<void> {
|
|
logger.debug(
|
|
{ registryUrl, api },
|
|
'Registry does not implement the crates.io read API, skipping crate metadata and release timestamp lookups',
|
|
);
|
|
memCache.set(`crate-datasource/registry-api-unsupported/${api}`, true);
|
|
await packageCache.set('datasource-crate-registry-api', api, true, 24 * 60);
|
|
}
|
|
|
|
private async _getCrateMetadata(
|
|
info: RegistryInfo,
|
|
packageName: string,
|
|
): Promise<CrateMetadata | null> {
|
|
const registryConfig = await this.fetchRegistryConfig(info);
|
|
if (!registryConfig?.api) {
|
|
return null;
|
|
}
|
|
|
|
const { api } = registryConfig;
|
|
if (await this.isReadApiUnsupported(api)) {
|
|
return null;
|
|
}
|
|
|
|
const apiBaseUrl = joinUrlParts(api, 'api/v1/');
|
|
|
|
// The `?include=` suffix is required to avoid unnecessary database queries
|
|
// on the crates.io server. This lets us work around the regular request
|
|
// throttling of one request per second.
|
|
const crateUrl = `${apiBaseUrl}crates/${packageName}?include=`;
|
|
|
|
logger.trace(
|
|
{ crateUrl, packageName, registryUrl: info.rawUrl },
|
|
'downloading crate metadata',
|
|
);
|
|
|
|
try {
|
|
const { body } = await this.http.getJson(crateUrl, CrateMetadataResponse);
|
|
return body.crate;
|
|
} catch (err) {
|
|
// The index lookup preceding this call found the crate, so a 404 means
|
|
// the registry does not implement the endpoint at all
|
|
if (
|
|
!CrateDatasource.isCratesIoApi(api) &&
|
|
CrateDatasource.isNotFound(err)
|
|
) {
|
|
await this.markReadApiUnsupported(info.rawUrl, api);
|
|
return null;
|
|
}
|
|
logger.debug(
|
|
{ err, packageName, registryUrl: info.rawUrl },
|
|
'failed to download crate metadata',
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public getCrateMetadata(
|
|
info: RegistryInfo,
|
|
packageName: string,
|
|
): Promise<CrateMetadata | null> {
|
|
return withCache(
|
|
{
|
|
namespace: `datasource-${CrateDatasource.id}-metadata`,
|
|
key: `${info.rawUrl}/${packageName}`,
|
|
cacheable: info.flavor === 'crates.io',
|
|
ttlMinutes: 24 * 60, // 24 hours
|
|
},
|
|
() => this._getCrateMetadata(info, packageName),
|
|
);
|
|
}
|
|
|
|
public async fetchCrateRecordsPayload(
|
|
info: RegistryInfo,
|
|
packageName: string,
|
|
): Promise<string> {
|
|
if (info.clonePath) {
|
|
const path = upath.join(
|
|
info.clonePath,
|
|
...CrateDatasource.getIndexSuffix(packageName),
|
|
);
|
|
return readCacheFile(path, 'utf8');
|
|
}
|
|
|
|
// sparse index
|
|
const packageSuffix = CrateDatasource.getIndexSuffix(
|
|
packageName.toLowerCase(),
|
|
);
|
|
const crateUrl = joinUrlParts(info.rawUrl, ...packageSuffix);
|
|
logger.trace(
|
|
{ crateUrl, packageName, registryUrl: info.rawUrl },
|
|
'fetching crate records from sparse index',
|
|
);
|
|
try {
|
|
return (await this.http.getText(crateUrl)).body;
|
|
} catch (err) {
|
|
this.handleGenericErrors(err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Computes the dependency URL for a crate, given
|
|
* registry information
|
|
*/
|
|
private static getDependencyUrl(
|
|
info: RegistryInfo,
|
|
packageName: string,
|
|
): string {
|
|
switch (info.flavor) {
|
|
case 'crates.io':
|
|
return `https://crates.io/crates/${packageName}`;
|
|
case 'cloudsmith': {
|
|
// input: https://dl.cloudsmith.io/basic/$org/$repo/cargo/index.git
|
|
const tokens = info.url.pathname.split('/');
|
|
const org = tokens[2];
|
|
const repo = tokens[3];
|
|
return `https://cloudsmith.io/~${org}/repos/${repo}/packages/detail/cargo/${packageName}`;
|
|
}
|
|
default:
|
|
return `${info.url}/${packageName}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a Git URL, computes a semi-human-readable name for a folder in which to
|
|
* clone the repository.
|
|
*/
|
|
private static cacheDirFromUrl(url: URL): string {
|
|
const proto = url.protocol.replace(regEx(/:$/), '');
|
|
const host = url.hostname;
|
|
const hash = toSha256(url.pathname).substring(0, 7);
|
|
|
|
return `crate-registry-${proto}-${host}-${hash}`;
|
|
}
|
|
|
|
private static isSparseRegistry(url: string): boolean {
|
|
const parsed = parseUrl(url);
|
|
if (!parsed) {
|
|
return false;
|
|
}
|
|
return parsed.protocol.startsWith('sparse+');
|
|
}
|
|
|
|
/**
|
|
* Fetches information about a registry, by url.
|
|
* If no url is given, assumes crates.io.
|
|
* If an url is given, assumes it's a valid Git repository
|
|
* url and clones it to cache.
|
|
*/
|
|
private static async fetchRegistryInfo({
|
|
packageName,
|
|
registryUrl,
|
|
}: GetReleasesConfig): Promise<RegistryInfo | null> {
|
|
/* v8 ignore next -- should never happen */
|
|
if (!registryUrl) {
|
|
return null;
|
|
}
|
|
|
|
const isSparseRegistry = CrateDatasource.isSparseRegistry(registryUrl);
|
|
const registryFetchUrl = isSparseRegistry
|
|
? registryUrl.replace(regEx(/^sparse\+/), '')
|
|
: registryUrl;
|
|
|
|
const url = parseUrl(registryFetchUrl);
|
|
if (!url) {
|
|
logger.debug(`Could not parse registry URL ${registryFetchUrl}`);
|
|
return null;
|
|
}
|
|
|
|
let flavor: RegistryFlavor;
|
|
if (url.hostname === 'index.crates.io') {
|
|
flavor = 'crates.io';
|
|
} else if (url.hostname === 'dl.cloudsmith.io') {
|
|
flavor = 'cloudsmith';
|
|
} else {
|
|
flavor = 'other';
|
|
}
|
|
|
|
const registry: RegistryInfo = {
|
|
flavor,
|
|
rawUrl: registryFetchUrl,
|
|
url,
|
|
};
|
|
|
|
if (
|
|
registry.flavor !== 'crates.io' &&
|
|
!GlobalConfig.get('allowCustomCrateRegistries')
|
|
) {
|
|
logger.warn(
|
|
'crate datasource: allowCustomCrateRegistries=true is required for registries other than crates.io, bailing out',
|
|
);
|
|
return null;
|
|
}
|
|
if (!isSparseRegistry) {
|
|
const cacheKey = `crate-datasource/registry-clone-path/${registryFetchUrl}`;
|
|
const lockKey = registryFetchUrl;
|
|
|
|
const executionTimeout = GlobalConfig.get('executionTimeout') * 60 * 1000;
|
|
const gitTimeout = GlobalConfig.get('gitTimeout') || executionTimeout;
|
|
const releaseLock = await acquireLock(
|
|
lockKey,
|
|
'crate-registry',
|
|
gitTimeout,
|
|
);
|
|
try {
|
|
const cached = memCache.get<CloneResult>(cacheKey);
|
|
|
|
if (cached?.err) {
|
|
logger.warn(
|
|
{ err: cached.err, packageName, registryFetchUrl },
|
|
'Previous git clone failed, bailing out.',
|
|
);
|
|
return null;
|
|
}
|
|
|
|
if (cached?.clonePath) {
|
|
registry.clonePath = cached.clonePath;
|
|
return registry;
|
|
}
|
|
|
|
const clonePath = upath.join(
|
|
privateCacheDir(),
|
|
CrateDatasource.cacheDirFromUrl(url),
|
|
);
|
|
|
|
const result = await CrateDatasource.clone(
|
|
registryFetchUrl,
|
|
clonePath,
|
|
packageName,
|
|
);
|
|
|
|
memCache.set(cacheKey, result);
|
|
|
|
if (result.err) {
|
|
logger.warn(
|
|
{ err: result.err, packageName, registryFetchUrl },
|
|
'Git clone failed, bailing out.',
|
|
);
|
|
return null;
|
|
}
|
|
|
|
registry.clonePath = result.clonePath;
|
|
} finally {
|
|
releaseLock();
|
|
}
|
|
}
|
|
|
|
return registry;
|
|
}
|
|
|
|
private static async clone(
|
|
registryFetchUrl: string,
|
|
clonePath: string,
|
|
packageName: string,
|
|
): Promise<CloneResult> {
|
|
logger.info(
|
|
{ clonePath, registryFetchUrl },
|
|
`Cloning private cargo registry`,
|
|
);
|
|
|
|
const git = createSimpleGit({
|
|
config: { maxConcurrentProcesses: 1 },
|
|
});
|
|
|
|
try {
|
|
await git.clone(registryFetchUrl, clonePath, {
|
|
'--depth': 1,
|
|
});
|
|
return { clonePath };
|
|
} catch (err) {
|
|
if (
|
|
err.message.includes(
|
|
'fatal: dumb http transport does not support shallow capabilities',
|
|
)
|
|
) {
|
|
logger.info(
|
|
{ packageName, registryFetchUrl },
|
|
'failed to shallow clone git registry, doing full clone',
|
|
);
|
|
try {
|
|
await git.clone(registryFetchUrl, clonePath);
|
|
return { clonePath };
|
|
} catch (err) {
|
|
logger.warn(
|
|
{ err, packageName, registryFetchUrl },
|
|
'failed cloning git registry',
|
|
);
|
|
return { err };
|
|
}
|
|
} else {
|
|
logger.warn(
|
|
{ err, packageName, registryFetchUrl },
|
|
'failed cloning git registry',
|
|
);
|
|
return { err };
|
|
}
|
|
}
|
|
}
|
|
|
|
private static isCratesIo(registryUrl: string | undefined): boolean {
|
|
if (!registryUrl) {
|
|
return false;
|
|
}
|
|
const normalized = registryUrl.replace(regEx(/^sparse\+/), '');
|
|
const parsed = parseUrl(normalized);
|
|
return parsed?.hostname === 'index.crates.io';
|
|
}
|
|
|
|
public static getIndexSuffix(packageName: string): string[] {
|
|
const len = packageName.length;
|
|
|
|
if (len === 1) {
|
|
return ['1', packageName];
|
|
}
|
|
if (len === 2) {
|
|
return ['2', packageName];
|
|
}
|
|
if (len === 3) {
|
|
return ['3', packageName[0], packageName];
|
|
}
|
|
|
|
return [packageName.slice(0, 2), packageName.slice(2, 4), packageName];
|
|
}
|
|
|
|
private async _postprocessRelease(
|
|
{ packageName, registryUrl }: PostprocessReleaseConfig,
|
|
release: Release,
|
|
): Promise<PostprocessReleaseResult> {
|
|
if (release.releaseTimestamp) {
|
|
return release;
|
|
}
|
|
|
|
// Look up the registry config from cache (populated during getReleases)
|
|
const rawUrl = registryUrl?.replace(regEx(/^sparse\+/), '');
|
|
if (!rawUrl) {
|
|
return release;
|
|
}
|
|
const cacheKey = `crate-datasource/registry-config/${rawUrl}`;
|
|
const config = memCache.get<RegistryConfig>(cacheKey);
|
|
if (!config?.api) {
|
|
return release;
|
|
}
|
|
|
|
const { api } = config;
|
|
if (await this.isReadApiUnsupported(api)) {
|
|
return release;
|
|
}
|
|
|
|
const apiBaseUrl = joinUrlParts(api, 'api/v1/');
|
|
const url = `${apiBaseUrl}crates/${packageName}/${release.versionOrig ?? release.version}`;
|
|
logger.trace(
|
|
{ url, packageName, version: release.version, registryUrl },
|
|
'fetching crate release timestamp',
|
|
);
|
|
// Getting release timestamp could become unnecessary if the manual backfill of `pubtime` mentioned in
|
|
// https://github.com/rust-lang/cargo/issues/15491 is done for all packages.
|
|
try {
|
|
const { body: releaseTimestamp } = await this.http.getJson(
|
|
url,
|
|
{ cacheProvider: memCacheProvider },
|
|
ReleaseTimestamp,
|
|
);
|
|
release.releaseTimestamp = releaseTimestamp;
|
|
} catch (err) {
|
|
if (
|
|
CrateDatasource.isCratesIoApi(api) ||
|
|
!CrateDatasource.isNotFound(err)
|
|
) {
|
|
throw err;
|
|
}
|
|
await this.markReadApiUnsupported(rawUrl, api);
|
|
}
|
|
return release;
|
|
}
|
|
|
|
override postprocessRelease(
|
|
config: PostprocessReleaseConfig,
|
|
release: Release,
|
|
): Promise<PostprocessReleaseResult> {
|
|
return withCache(
|
|
{
|
|
namespace: `datasource-crate`,
|
|
key: `postprocessRelease:${config.registryUrl}:${config.packageName}:${release.version}`,
|
|
ttlMinutes: 7 * 24 * 60,
|
|
cacheable: CrateDatasource.isCratesIo(config.registryUrl ?? undefined),
|
|
},
|
|
() => this._postprocessRelease(config, release),
|
|
);
|
|
}
|
|
}
|