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 * as httpMock from '../../../../test/http-mock';
|
||||||
|
import { logger } from '../../../../test/util';
|
||||||
import { DenoDatasource } from '.';
|
import { DenoDatasource } from '.';
|
||||||
|
|
||||||
describe('modules/datasource/deno/index', () => {
|
describe('modules/datasource/deno/index', () => {
|
||||||
|
@ -10,7 +12,7 @@ describe('modules/datasource/deno/index', () => {
|
||||||
.scope(deno.defaultRegistryUrls[0])
|
.scope(deno.defaultRegistryUrls[0])
|
||||||
.get('/v2/modules/std')
|
.get('/v2/modules/std')
|
||||||
.reply(200, {
|
.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' }],
|
tags: [{ value: 'top_5_percent', kind: 'popularity' }],
|
||||||
})
|
})
|
||||||
.get('/v2/modules/std/0.163.0')
|
.get('/v2/modules/std/0.163.0')
|
||||||
|
@ -32,7 +34,9 @@ describe('modules/datasource/deno/index', () => {
|
||||||
type: 'github',
|
type: 'github',
|
||||||
},
|
},
|
||||||
uploaded_at: '2022-10-20T12:10:21.592Z',
|
uploaded_at: '2022-10-20T12:10:21.592Z',
|
||||||
});
|
})
|
||||||
|
.get('/v2/modules/std/0.161.0')
|
||||||
|
.reply(200, { foo: 'bar' });
|
||||||
|
|
||||||
const result = await deno.getReleases({
|
const result = await deno.getReleases({
|
||||||
packageName: 'https://deno.land/std',
|
packageName: 'https://deno.land/std',
|
||||||
|
@ -50,11 +54,21 @@ describe('modules/datasource/deno/index', () => {
|
||||||
sourceUrl: 'https://github.com/denoland/deno_std',
|
sourceUrl: 'https://github.com/denoland/deno_std',
|
||||||
releaseTimestamp: '2022-10-20T12:10:21.592Z',
|
releaseTimestamp: '2022-10-20T12:10:21.592Z',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: '0.161.0',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
tags: {
|
tags: {
|
||||||
popularity: 'top_5_percent',
|
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 () => {
|
it('throws error if module endpoint fails', async () => {
|
||||||
|
|
|
@ -9,12 +9,7 @@ import * as semanticVersioning from '../../versioning/semver';
|
||||||
import { Datasource } from '../datasource';
|
import { Datasource } from '../datasource';
|
||||||
import type { Release } from '../index';
|
import type { Release } from '../index';
|
||||||
import type { GetReleasesConfig, ReleaseResult } from '../types';
|
import type { GetReleasesConfig, ReleaseResult } from '../types';
|
||||||
import type {
|
import { DenoAPIModuleResponse, DenoAPIModuleVersionResponse } from './schema';
|
||||||
DenoAPIModuleResponse,
|
|
||||||
DenoAPIModuleVersionResponse,
|
|
||||||
ReleaseMap,
|
|
||||||
} from './types';
|
|
||||||
import { createSourceURL, tagsToRecord } from './utils';
|
|
||||||
|
|
||||||
export class DenoDatasource extends Datasource {
|
export class DenoDatasource extends Datasource {
|
||||||
static readonly id = 'deno';
|
static readonly id = 'deno';
|
||||||
|
@ -45,7 +40,7 @@ export class DenoDatasource extends Datasource {
|
||||||
const massagedRegistryUrl = registryUrl!;
|
const massagedRegistryUrl = registryUrl!;
|
||||||
|
|
||||||
const extractResult = regEx(
|
const extractResult = regEx(
|
||||||
'^(https://deno.land/)(?<rawPackageName>[^@\\s]+)'
|
/^(https:\/\/deno.land\/)(?<rawPackageName>[^@\s]+)/
|
||||||
).exec(packageName);
|
).exec(packageName);
|
||||||
const rawPackageName = extractResult?.groups?.rawPackageName;
|
const rawPackageName = extractResult?.groups?.rawPackageName;
|
||||||
if (is.nullOrUndefined(rawPackageName)) {
|
if (is.nullOrUndefined(rawPackageName)) {
|
||||||
|
@ -73,17 +68,17 @@ export class DenoDatasource extends Datasource {
|
||||||
key: (moduleAPIURL) => moduleAPIURL,
|
key: (moduleAPIURL) => moduleAPIURL,
|
||||||
})
|
})
|
||||||
async getReleaseResult(moduleAPIURL: string): Promise<ReleaseResult> {
|
async getReleaseResult(moduleAPIURL: string): Promise<ReleaseResult> {
|
||||||
const { versions, tags } = (
|
const releasesCache: Record<string, Release> =
|
||||||
await this.http.getJson<DenoAPIModuleResponse>(moduleAPIURL)
|
(await packageCache.get(
|
||||||
).body;
|
|
||||||
|
|
||||||
const releasesCache =
|
|
||||||
(await packageCache.get<ReleaseMap>(
|
|
||||||
`datasource-${DenoDatasource.id}-details`,
|
`datasource-${DenoDatasource.id}-details`,
|
||||||
moduleAPIURL
|
moduleAPIURL
|
||||||
)) ?? {};
|
)) ?? {};
|
||||||
let cacheModified = false;
|
let cacheModified = false;
|
||||||
|
|
||||||
|
const {
|
||||||
|
body: { versions, tags },
|
||||||
|
} = await this.http.getJson(moduleAPIURL, DenoAPIModuleResponse);
|
||||||
|
|
||||||
// get details for the versions
|
// get details for the versions
|
||||||
const releases = await pMap(
|
const releases = await pMap(
|
||||||
versions,
|
versions,
|
||||||
|
@ -95,8 +90,16 @@ export class DenoDatasource extends Datasource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://apiland.deno.dev/v2/modules/postgres/v0.17.0
|
// https://apiland.deno.dev/v2/modules/postgres/v0.17.0
|
||||||
const release = await this.getReleaseDetails(
|
const url = joinUrlParts(moduleAPIURL, version);
|
||||||
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;
|
releasesCache[release.version] = release;
|
||||||
|
@ -117,21 +120,6 @@ export class DenoDatasource extends Datasource {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { releases, tags };
|
||||||
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),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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