mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-14 16:46:25 +00:00
refactor(datasource/deno): Add schema validation (#21329)
This commit is contained in:
parent
3b53efe67c
commit
00c8f9055e
5 changed files with 79 additions and 89 deletions
|
@ -1,4 +1,6 @@
|
|||
import { ZodError } from 'zod';
|
||||
import * as httpMock from '../../../../test/http-mock';
|
||||
import { logger } from '../../../../test/util';
|
||||
import { DenoDatasource } from '.';
|
||||
|
||||
describe('modules/datasource/deno/index', () => {
|
||||
|
@ -10,7 +12,7 @@ describe('modules/datasource/deno/index', () => {
|
|||
.scope(deno.defaultRegistryUrls[0])
|
||||
.get('/v2/modules/std')
|
||||
.reply(200, {
|
||||
versions: ['0.163.0', '0.162.0'],
|
||||
versions: ['0.163.0', '0.162.0', '0.161.0'],
|
||||
tags: [{ value: 'top_5_percent', kind: 'popularity' }],
|
||||
})
|
||||
.get('/v2/modules/std/0.163.0')
|
||||
|
@ -32,7 +34,9 @@ describe('modules/datasource/deno/index', () => {
|
|||
type: 'github',
|
||||
},
|
||||
uploaded_at: '2022-10-20T12:10:21.592Z',
|
||||
});
|
||||
})
|
||||
.get('/v2/modules/std/0.161.0')
|
||||
.reply(200, { foo: 'bar' });
|
||||
|
||||
const result = await deno.getReleases({
|
||||
packageName: 'https://deno.land/std',
|
||||
|
@ -50,11 +54,21 @@ describe('modules/datasource/deno/index', () => {
|
|||
sourceUrl: 'https://github.com/denoland/deno_std',
|
||||
releaseTimestamp: '2022-10-20T12:10:21.592Z',
|
||||
},
|
||||
{
|
||||
version: '0.161.0',
|
||||
},
|
||||
],
|
||||
tags: {
|
||||
popularity: 'top_5_percent',
|
||||
},
|
||||
});
|
||||
|
||||
expect(logger.logger.warn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
err: expect.any(ZodError),
|
||||
}),
|
||||
`Deno: failed to get version details for 0.161.0`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error if module endpoint fails', async () => {
|
||||
|
|
|
@ -9,12 +9,7 @@ import * as semanticVersioning from '../../versioning/semver';
|
|||
import { Datasource } from '../datasource';
|
||||
import type { Release } from '../index';
|
||||
import type { GetReleasesConfig, ReleaseResult } from '../types';
|
||||
import type {
|
||||
DenoAPIModuleResponse,
|
||||
DenoAPIModuleVersionResponse,
|
||||
ReleaseMap,
|
||||
} from './types';
|
||||
import { createSourceURL, tagsToRecord } from './utils';
|
||||
import { DenoAPIModuleResponse, DenoAPIModuleVersionResponse } from './schema';
|
||||
|
||||
export class DenoDatasource extends Datasource {
|
||||
static readonly id = 'deno';
|
||||
|
@ -45,7 +40,7 @@ export class DenoDatasource extends Datasource {
|
|||
const massagedRegistryUrl = registryUrl!;
|
||||
|
||||
const extractResult = regEx(
|
||||
'^(https://deno.land/)(?<rawPackageName>[^@\\s]+)'
|
||||
/^(https:\/\/deno.land\/)(?<rawPackageName>[^@\s]+)/
|
||||
).exec(packageName);
|
||||
const rawPackageName = extractResult?.groups?.rawPackageName;
|
||||
if (is.nullOrUndefined(rawPackageName)) {
|
||||
|
@ -73,17 +68,17 @@ export class DenoDatasource extends Datasource {
|
|||
key: (moduleAPIURL) => moduleAPIURL,
|
||||
})
|
||||
async getReleaseResult(moduleAPIURL: string): Promise<ReleaseResult> {
|
||||
const { versions, tags } = (
|
||||
await this.http.getJson<DenoAPIModuleResponse>(moduleAPIURL)
|
||||
).body;
|
||||
|
||||
const releasesCache =
|
||||
(await packageCache.get<ReleaseMap>(
|
||||
const releasesCache: Record<string, Release> =
|
||||
(await packageCache.get(
|
||||
`datasource-${DenoDatasource.id}-details`,
|
||||
moduleAPIURL
|
||||
)) ?? {};
|
||||
let cacheModified = false;
|
||||
|
||||
const {
|
||||
body: { versions, tags },
|
||||
} = await this.http.getJson(moduleAPIURL, DenoAPIModuleResponse);
|
||||
|
||||
// get details for the versions
|
||||
const releases = await pMap(
|
||||
versions,
|
||||
|
@ -95,8 +90,16 @@ export class DenoDatasource extends Datasource {
|
|||
}
|
||||
|
||||
// https://apiland.deno.dev/v2/modules/postgres/v0.17.0
|
||||
const release = await this.getReleaseDetails(
|
||||
joinUrlParts(moduleAPIURL, version)
|
||||
const url = joinUrlParts(moduleAPIURL, version);
|
||||
const { body: release } = await this.http.getJson(
|
||||
url,
|
||||
DenoAPIModuleVersionResponse.catch(({ error: err }) => {
|
||||
logger.warn(
|
||||
{ err },
|
||||
`Deno: failed to get version details for ${version}`
|
||||
);
|
||||
return { version };
|
||||
})
|
||||
);
|
||||
|
||||
releasesCache[release.version] = release;
|
||||
|
@ -117,21 +120,6 @@ export class DenoDatasource extends Datasource {
|
|||
);
|
||||
}
|
||||
|
||||
return {
|
||||
releases,
|
||||
tags: tagsToRecord(tags),
|
||||
};
|
||||
}
|
||||
|
||||
async getReleaseDetails(moduleAPIVersionURL: string): Promise<Release> {
|
||||
const { version, uploaded_at, upload_options } = (
|
||||
await this.http.getJson<DenoAPIModuleVersionResponse>(moduleAPIVersionURL)
|
||||
).body;
|
||||
return {
|
||||
version,
|
||||
gitRef: upload_options.ref,
|
||||
releaseTimestamp: uploaded_at,
|
||||
sourceUrl: createSourceURL(upload_options),
|
||||
};
|
||||
return { releases, tags };
|
||||
}
|
||||
}
|
||||
|
|
44
lib/modules/datasource/deno/schema.ts
Normal file
44
lib/modules/datasource/deno/schema.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { z } from 'zod';
|
||||
import { getSourceUrl as getGithubSourceUrl } from '../../../util/github/url';
|
||||
import { looseArray } from '../../../util/schema-utils';
|
||||
import type { Release } from '../types';
|
||||
|
||||
export const DenoApiTag = z.object({
|
||||
kind: z.string(),
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
export const DenoAPIModuleResponse = z.object({
|
||||
tags: looseArray(DenoApiTag).transform((tags) => {
|
||||
const record: Record<string, string> = {};
|
||||
for (const { kind, value } of tags) {
|
||||
record[kind] = value;
|
||||
}
|
||||
return record;
|
||||
}),
|
||||
versions: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const DenoAPIUploadOptions = z.object({
|
||||
ref: z.string(),
|
||||
type: z.union([z.literal('github'), z.unknown()]),
|
||||
repository: z.string(),
|
||||
subdir: z.string().optional(),
|
||||
});
|
||||
|
||||
export const DenoAPIModuleVersionResponse = z
|
||||
.object({
|
||||
upload_options: DenoAPIUploadOptions,
|
||||
uploaded_at: z.string(),
|
||||
version: z.string(),
|
||||
})
|
||||
.transform(
|
||||
({ version, uploaded_at: releaseTimestamp, upload_options }): Release => {
|
||||
let sourceUrl: string | undefined = undefined;
|
||||
const { type, repository, ref: gitRef } = upload_options;
|
||||
if (type === 'github') {
|
||||
sourceUrl = getGithubSourceUrl(repository);
|
||||
}
|
||||
return { version, gitRef, releaseTimestamp, sourceUrl };
|
||||
}
|
||||
);
|
|
@ -1,34 +0,0 @@
|
|||
import type { Release } from '../types';
|
||||
|
||||
export interface DenoAPIModuleResponse {
|
||||
name: string;
|
||||
latest_version: string;
|
||||
star_count: number;
|
||||
popularity_score: number;
|
||||
tags: DenoAPITags[];
|
||||
versions: string[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface DenoAPIModuleVersionResponse {
|
||||
upload_options: DenoAPIUploadOptions;
|
||||
analysis_version: string;
|
||||
description: string;
|
||||
uploaded_at: string; // ISO date
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface DenoAPIUploadOptions {
|
||||
ref: string; // commit ref / tag
|
||||
type: 'github' | unknown; // type of hosting. seen: ['github']
|
||||
repository: string; // repo of hosting e.g. denodrivers/postgres
|
||||
subdir?: string;
|
||||
}
|
||||
|
||||
export interface DenoAPITags {
|
||||
kind: string; // e.g. popularity
|
||||
value: string; // e.g. top_5_percent
|
||||
}
|
||||
|
||||
export type ReleaseMap = Record<string, Release>;
|
|
@ -1,22 +0,0 @@
|
|||
import { getSourceUrl as getGithubSourceUrl } from '../../../util/github/url';
|
||||
import type { DenoAPITags, DenoAPIUploadOptions } from './types';
|
||||
|
||||
export function createSourceURL({
|
||||
type,
|
||||
repository,
|
||||
}: DenoAPIUploadOptions): string | undefined {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return getGithubSourceUrl(repository);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function tagsToRecord(tags: DenoAPITags[]): Record<string, string> {
|
||||
const record: Record<string, string> = {};
|
||||
for (const { kind, value } of tags) {
|
||||
record[kind] = value;
|
||||
}
|
||||
return record;
|
||||
}
|
Loading…
Reference in a new issue