feat: add releases widget (#2497)
Co-authored-by: Andre Silva <asilva01@acuitysso.com> Co-authored-by: Meier Lukas <meierschlumpf@gmail.com> Co-authored-by: Manuel <30572287+manuel-rw@users.noreply.github.com>
This commit is contained in:
306
packages/request-handler/src/releases-providers.ts
Normal file
306
packages/request-handler/src/releases-providers.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export interface ReleasesProvider {
|
||||
getDetailsUrl: (identifier: string) => string | undefined;
|
||||
parseDetailsResponse: (response: unknown) => z.SafeParseReturnType<unknown, DetailsResponse> | undefined;
|
||||
getReleasesUrl: (identifier: string) => string;
|
||||
parseReleasesResponse: (response: unknown) => z.SafeParseReturnType<unknown, ReleasesResponse[]>;
|
||||
}
|
||||
|
||||
interface ProvidersProps {
|
||||
[key: string]: ReleasesProvider;
|
||||
DockerHub: ReleasesProvider;
|
||||
Github: ReleasesProvider;
|
||||
Gitlab: ReleasesProvider;
|
||||
Npm: ReleasesProvider;
|
||||
Codeberg: ReleasesProvider;
|
||||
}
|
||||
|
||||
export const Providers: ProvidersProps = {
|
||||
DockerHub: {
|
||||
getDetailsUrl(identifier) {
|
||||
if (identifier.indexOf("/") > 0) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
return "";
|
||||
}
|
||||
return `https://hub.docker.com/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`;
|
||||
} else {
|
||||
return `https://hub.docker.com/v2/repositories/library/${encodeURIComponent(identifier)}`;
|
||||
}
|
||||
},
|
||||
parseDetailsResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
name: z.string(),
|
||||
namespace: z.string(),
|
||||
description: z.string(),
|
||||
star_count: z.number(),
|
||||
date_registered: z.string().transform((value) => new Date(value)),
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: `https://hub.docker.com/r/${resp.namespace === "library" ? "_" : resp.namespace}/${resp.name}`,
|
||||
projectDescription: resp.description,
|
||||
isFork: false,
|
||||
isArchived: false,
|
||||
createdAt: resp.date_registered,
|
||||
starsCount: resp.star_count,
|
||||
openIssues: 0,
|
||||
forksCount: 0,
|
||||
}))
|
||||
.safeParse(response);
|
||||
},
|
||||
getReleasesUrl(identifier) {
|
||||
return `${this.getDetailsUrl(identifier)}/tags?page_size=200`;
|
||||
},
|
||||
parseReleasesResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
results: z.array(
|
||||
z
|
||||
.object({ name: z.string(), last_updated: z.string().transform((value) => new Date(value)) })
|
||||
.transform((tag) => ({
|
||||
identifier: "",
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: tag.last_updated,
|
||||
})),
|
||||
),
|
||||
})
|
||||
.transform((resp) => {
|
||||
return resp.results.map((release) => ({
|
||||
...release,
|
||||
releaseUrl: "",
|
||||
releaseDescription: "",
|
||||
isPreRelease: false,
|
||||
}));
|
||||
})
|
||||
.safeParse(response);
|
||||
},
|
||||
},
|
||||
Github: {
|
||||
getDetailsUrl(identifier) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
return "";
|
||||
}
|
||||
return `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
|
||||
},
|
||||
parseDetailsResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
html_url: z.string(),
|
||||
description: z.string(),
|
||||
fork: z.boolean(),
|
||||
archived: z.boolean(),
|
||||
created_at: z.string().transform((value) => new Date(value)),
|
||||
stargazers_count: z.number(),
|
||||
open_issues_count: z.number(),
|
||||
forks_count: z.number(),
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: resp.html_url,
|
||||
projectDescription: resp.description,
|
||||
isFork: resp.fork,
|
||||
isArchived: resp.archived,
|
||||
createdAt: resp.created_at,
|
||||
starsCount: resp.stargazers_count,
|
||||
openIssues: resp.open_issues_count,
|
||||
forksCount: resp.forks_count,
|
||||
}))
|
||||
.safeParse(response);
|
||||
},
|
||||
getReleasesUrl(identifier) {
|
||||
return `${this.getDetailsUrl(identifier)}/releases`;
|
||||
},
|
||||
parseReleasesResponse(response) {
|
||||
return z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
tag_name: z.string(),
|
||||
published_at: z.string().transform((value) => new Date(value)),
|
||||
html_url: z.string(),
|
||||
body: z.string(),
|
||||
prerelease: z.boolean(),
|
||||
})
|
||||
.transform((tag) => ({
|
||||
identifier: "",
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.html_url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
})),
|
||||
)
|
||||
.safeParse(response);
|
||||
},
|
||||
},
|
||||
Gitlab: {
|
||||
getDetailsUrl(identifier) {
|
||||
return `https://gitlab.com/api/v4/projects/${encodeURIComponent(identifier)}`;
|
||||
},
|
||||
parseDetailsResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
web_url: z.string(),
|
||||
description: z.string(),
|
||||
forked_from_project: z.object({ id: z.number() }).nullable(),
|
||||
archived: z.boolean(),
|
||||
created_at: z.string().transform((value) => new Date(value)),
|
||||
star_count: z.number(),
|
||||
open_issues_count: z.number(),
|
||||
forks_count: z.number(),
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: resp.web_url,
|
||||
projectDescription: resp.description,
|
||||
isFork: resp.forked_from_project !== null,
|
||||
isArchived: resp.archived,
|
||||
createdAt: resp.created_at,
|
||||
starsCount: resp.star_count,
|
||||
openIssues: resp.open_issues_count,
|
||||
forksCount: resp.forks_count,
|
||||
}))
|
||||
.safeParse(response);
|
||||
},
|
||||
getReleasesUrl(identifier) {
|
||||
return `${this.getDetailsUrl(identifier)}/releases`;
|
||||
},
|
||||
parseReleasesResponse(response) {
|
||||
return z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
name: z.string(),
|
||||
released_at: z.string().transform((value) => new Date(value)),
|
||||
description: z.string(),
|
||||
_links: z.object({ self: z.string() }),
|
||||
upcoming_release: z.boolean(),
|
||||
})
|
||||
.transform((tag) => ({
|
||||
identifier: "",
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: tag.released_at,
|
||||
releaseUrl: tag._links.self,
|
||||
releaseDescription: tag.description,
|
||||
isPreRelease: tag.upcoming_release,
|
||||
})),
|
||||
)
|
||||
.safeParse(response);
|
||||
},
|
||||
},
|
||||
Npm: {
|
||||
getDetailsUrl(_) {
|
||||
return undefined;
|
||||
},
|
||||
parseDetailsResponse(_) {
|
||||
return undefined;
|
||||
},
|
||||
getReleasesUrl(identifier) {
|
||||
return `https://registry.npmjs.org/${encodeURIComponent(identifier)}`;
|
||||
},
|
||||
parseReleasesResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
time: z.record(z.string().transform((value) => new Date(value))).transform((version) =>
|
||||
Object.entries(version).map(([key, value]) => ({
|
||||
identifier: "",
|
||||
latestRelease: key,
|
||||
latestReleaseAt: value,
|
||||
})),
|
||||
),
|
||||
versions: z.record(z.object({ description: z.string() })),
|
||||
name: z.string(),
|
||||
})
|
||||
.transform((resp) => {
|
||||
return resp.time.map((release) => ({
|
||||
...release,
|
||||
releaseUrl: `https://www.npmjs.com/package/${resp.name}/v/${release.latestRelease}`,
|
||||
releaseDescription: resp.versions[release.latestRelease]?.description ?? "",
|
||||
isPreRelease: false,
|
||||
}));
|
||||
})
|
||||
.safeParse(response);
|
||||
},
|
||||
},
|
||||
Codeberg: {
|
||||
getDetailsUrl(identifier) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
return "";
|
||||
}
|
||||
return `https://codeberg.org/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
|
||||
},
|
||||
parseDetailsResponse(response) {
|
||||
return z
|
||||
.object({
|
||||
html_url: z.string(),
|
||||
description: z.string(),
|
||||
fork: z.boolean(),
|
||||
archived: z.boolean(),
|
||||
created_at: z.string().transform((value) => new Date(value)),
|
||||
stars_count: z.number(),
|
||||
open_issues_count: z.number(),
|
||||
forks_count: z.number(),
|
||||
})
|
||||
.transform((resp) => ({
|
||||
projectUrl: resp.html_url,
|
||||
projectDescription: resp.description,
|
||||
isFork: resp.fork,
|
||||
isArchived: resp.archived,
|
||||
createdAt: resp.created_at,
|
||||
starsCount: resp.stars_count,
|
||||
openIssues: resp.open_issues_count,
|
||||
forksCount: resp.forks_count,
|
||||
}))
|
||||
.safeParse(response);
|
||||
},
|
||||
getReleasesUrl(identifier) {
|
||||
return `${this.getDetailsUrl(identifier)}/releases`;
|
||||
},
|
||||
parseReleasesResponse(response) {
|
||||
return z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
tag_name: z.string(),
|
||||
published_at: z.string().transform((value) => new Date(value)),
|
||||
url: z.string(),
|
||||
body: z.string(),
|
||||
prerelease: z.boolean(),
|
||||
})
|
||||
.transform((tag) => ({
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
})),
|
||||
)
|
||||
.safeParse(response);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const _detailsSchema = z.object({
|
||||
projectUrl: z.string(),
|
||||
projectDescription: z.string(),
|
||||
isFork: z.boolean(),
|
||||
isArchived: z.boolean(),
|
||||
createdAt: z.date(),
|
||||
starsCount: z.number(),
|
||||
openIssues: z.number(),
|
||||
forksCount: z.number(),
|
||||
});
|
||||
|
||||
const _releasesSchema = z.object({
|
||||
latestRelease: z.string(),
|
||||
latestReleaseAt: z.date(),
|
||||
releaseUrl: z.string(),
|
||||
releaseDescription: z.string(),
|
||||
isPreRelease: z.boolean(),
|
||||
});
|
||||
|
||||
export type DetailsResponse = z.infer<typeof _detailsSchema>;
|
||||
|
||||
export type ReleasesResponse = z.infer<typeof _releasesSchema>;
|
||||
Reference in New Issue
Block a user