Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
145bea727f | ||
|
|
5b7de36642 | ||
|
|
6f3b7ec491 | ||
|
|
bbfc6e48fe | ||
|
|
2fe36564c2 | ||
|
|
747bb922cc | ||
|
|
24def99827 | ||
|
|
e0526c5ea7 | ||
|
|
9e4b93abcf | ||
|
|
c544d6ec2e |
No files matched your search
@@ -1,3 +1,25 @@
|
||||
## [1.43.1](https://github.com/homarr-labs/homarr/compare/v1.43.0...v1.43.1) (2025-10-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency @auth/core to ^0.41.1 ([#4387](https://github.com/homarr-labs/homarr/issues/4387)) ([747bb92](https://github.com/homarr-labs/homarr/commit/747bb922ccf0508bd00c37faeab29e28876577b1))
|
||||
* **deps:** update dependency @auth/drizzle-adapter to ^1.11.1 ([#4388](https://github.com/homarr-labs/homarr/issues/4388)) ([2fe3656](https://github.com/homarr-labs/homarr/commit/2fe36564c26c33ae2625eb22ef4503c6dd8ebcf9))
|
||||
* **deps:** update dependency @jellyfin/sdk to ^0.12.0 ([#4359](https://github.com/homarr-labs/homarr/issues/4359)) ([554d19a](https://github.com/homarr-labs/homarr/commit/554d19a458e856141f90e795ecebabf205e945e9))
|
||||
* **deps:** update dependency @paralleldrive/cuid2 to v3 ([#4343](https://github.com/homarr-labs/homarr/issues/4343)) ([074d27e](https://github.com/homarr-labs/homarr/commit/074d27e37e3430e7e98cf28e2876afe009811027))
|
||||
* **deps:** update dependency drizzle-orm to ^0.44.7 ([#4372](https://github.com/homarr-labs/homarr/issues/4372)) ([8242bfc](https://github.com/homarr-labs/homarr/commit/8242bfc95cd0e67397b1bfc46d65aec3f568c304))
|
||||
* **deps:** update dependency ioredis to v5.8.2 ([#4357](https://github.com/homarr-labs/homarr/issues/4357)) ([3c4c26e](https://github.com/homarr-labs/homarr/commit/3c4c26ee214a37193aea31b69ce75322862fc534))
|
||||
* **deps:** update dependency mysql2 to v3.15.3 ([#4360](https://github.com/homarr-labs/homarr/issues/4360)) ([6e6c912](https://github.com/homarr-labs/homarr/commit/6e6c9122257934402848d6fdc51e6ccdfc2dcca0))
|
||||
* **deps:** update dependency next-auth to v5.0.0-beta.30 [security] ([#4379](https://github.com/homarr-labs/homarr/issues/4379)) ([c544d6e](https://github.com/homarr-labs/homarr/commit/c544d6ec2e426bc4c82db547d7c4a5dc4d6e2a27))
|
||||
* **deps:** update dependency next-intl to v4.4.0 ([#4365](https://github.com/homarr-labs/homarr/issues/4365)) ([ce63388](https://github.com/homarr-labs/homarr/commit/ce6338840c20fc46048687ed5566ff12fa4b2502))
|
||||
* **deps:** update dependency node-ical to ^0.22.1 ([#4346](https://github.com/homarr-labs/homarr/issues/4346)) ([ef18c58](https://github.com/homarr-labs/homarr/commit/ef18c58f5b09bafd697365a0182ab7bc8719db2b))
|
||||
* **deps:** update dependency superjson to v2.2.3 ([#4364](https://github.com/homarr-labs/homarr/issues/4364)) ([18cc04e](https://github.com/homarr-labs/homarr/commit/18cc04e7f2740226f7bc7c75c0b0cdaec7cda6f8))
|
||||
* **deps:** update dependency typescript-eslint to ^8.46.2 ([#4352](https://github.com/homarr-labs/homarr/issues/4352)) ([f96cbe0](https://github.com/homarr-labs/homarr/commit/f96cbe06525515112fad466cb8a5f33d74517aba))
|
||||
* **deps:** update testcontainers-node monorepo to ^11.7.2 ([#4366](https://github.com/homarr-labs/homarr/issues/4366)) ([7329135](https://github.com/homarr-labs/homarr/commit/7329135fd85906602a16a9bdc16a1ef1cc4607c5))
|
||||
* **deps:** update tiptap monorepo to v2.26.4 ([#4371](https://github.com/homarr-labs/homarr/issues/4371)) ([136ed8e](https://github.com/homarr-labs/homarr/commit/136ed8e8b401b801ba16f2079bdfb5aeebe9f57f))
|
||||
* **deps:** update trpc monorepo to ^11.7.0 ([#4385](https://github.com/homarr-labs/homarr/issues/4385)) ([24def99](https://github.com/homarr-labs/homarr/commit/24def9982709cd6de1765a7235169fe1ceb3cbea))
|
||||
* **media-releases:** support more media type mappings for jellyfin and emby ([#4382](https://github.com/homarr-labs/homarr/issues/4382)) ([6f3b7ec](https://github.com/homarr-labs/homarr/commit/6f3b7ec4915754e183300a915baebc11f264df62))
|
||||
* **releases-widget:** Search multiple docker hub pages for releases ([#4158](https://github.com/homarr-labs/homarr/issues/4158)) ([e0526c5](https://github.com/homarr-labs/homarr/commit/e0526c5ea7ee83adcd22d78fbf63334cb59bcf79))
|
||||
|
||||
## [1.43.0](https://github.com/homarr-labs/homarr/compare/v1.42.1...v1.43.0) (2025-10-24)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -61,10 +61,10 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.90.2",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/next": "^11.6.0",
|
||||
"@trpc/react-query": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/next": "^11.7.0",
|
||||
"@trpc/react-query": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homarr",
|
||||
"version": "1.43.0",
|
||||
"version": "1.43.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "cross-env CI=true turbo build",
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/react-query": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/tanstack-react-query": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/react-query": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@trpc/tanstack-react-query": "^11.7.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.5.6",
|
||||
"react": "19.2.0",
|
||||
|
||||
@@ -40,15 +40,22 @@ export const releasesRouter = createTRPCRouter({
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await Promise.all(
|
||||
input.repositories.map(async (repository) => {
|
||||
const innerHandler = releasesRequestHandler.handler(ctx.integration, {
|
||||
id: repository.id,
|
||||
identifier: repository.identifier,
|
||||
versionRegex: formatVersionFilterRegex(repository.versionFilter),
|
||||
});
|
||||
const response = await releasesRequestHandler
|
||||
.handler(ctx.integration, {
|
||||
id: repository.id,
|
||||
identifier: repository.identifier,
|
||||
versionRegex: formatVersionFilterRegex(repository.versionFilter),
|
||||
})
|
||||
.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
integration: { name: ctx.integration.name, kind: ctx.integration.kind },
|
||||
timestamp: response.timestamp,
|
||||
...response.data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.41.0",
|
||||
"@auth/drizzle-adapter": "^1.11.0",
|
||||
"@auth/core": "^0.41.1",
|
||||
"@auth/drizzle-adapter": "^1.11.1",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.9",
|
||||
"next": "15.5.6",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/tanstack-react-query": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@trpc/tanstack-react-query": "^11.7.0",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.41.0",
|
||||
"@auth/core": "^0.41.1",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
|
||||
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas";
|
||||
|
||||
@@ -43,22 +42,23 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Codeberg integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with Codeberg integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
@@ -66,34 +66,36 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
{ headers },
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const formattedReleases = data.map((tag) => ({
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
}));
|
||||
return getLatestRelease(formattedReleases, repository, details);
|
||||
}
|
||||
|
||||
const formattedReleases = data.map((tag) => ({
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
}));
|
||||
|
||||
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
}
|
||||
|
||||
protected async getDetailsAsync(owner: string, name: string): Promise<DetailsProviderResponse | undefined> {
|
||||
|
||||
@@ -14,8 +14,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas";
|
||||
|
||||
@@ -73,49 +72,61 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const relativeUrl = this.getRelativeUrl(repository.identifier);
|
||||
if (relativeUrl === "/") {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name' or 'name', for ${repository.identifier} on DockerHub`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const details = await this.getDetailsAsync(relativeUrl);
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100`), {
|
||||
headers,
|
||||
private parseIdentifier(identifier: string) {
|
||||
if (!identifier.includes("/")) return { owner: "", name: identifier };
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name' or 'name', for ${identifier} on DockerHub`, {
|
||||
identifier,
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const relativeUrl: `/${string}` = owner
|
||||
? `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`
|
||||
: `/v2/repositories/library/${encodeURIComponent(name)}`;
|
||||
|
||||
for (let page = 0; page <= 5; page++) {
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100&page=${page}`), {
|
||||
headers,
|
||||
});
|
||||
});
|
||||
if (!releasesResponse.ok) {
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
if (!releasesResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson
|
||||
? JSON.stringify(releasesResponseJson, null, 2)
|
||||
: releasesResult.error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const latestRelease = getLatestRelease(releasesResult.data.results, versionRegex);
|
||||
if (!latestRelease) continue;
|
||||
|
||||
const details = await this.getDetailsAsync(relativeUrl);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!releasesResult.success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : releasesResult.error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return getLatestRelease(releasesResult.data.results, repository, details);
|
||||
}
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
private async getDetailsAsync(relativeUrl: `/${string}`): Promise<DetailsProviderResponse | undefined> {
|
||||
@@ -154,18 +165,6 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
};
|
||||
}
|
||||
|
||||
private getRelativeUrl(identifier: string): `/${string}` {
|
||||
if (identifier.indexOf("/") > 0) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
return "/";
|
||||
}
|
||||
return `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`;
|
||||
} else {
|
||||
return `/v2/repositories/library/${encodeURIComponent(identifier)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async getSessionAsync(fetchAsync: typeof fetch = fetchWithTrustedCertificatesAsync): Promise<string> {
|
||||
const response = await fetchAsync(this.url("/v2/auth/token"), {
|
||||
method: "POST",
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
|
||||
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
|
||||
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
|
||||
import { convertJellyfinType } from "../jellyfin/jellyfin-integration";
|
||||
import type { IMediaReleasesIntegration, MediaRelease } from "../types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
|
||||
|
||||
const sessionSchema = z.object({
|
||||
NowPlayingItem: z
|
||||
@@ -163,7 +163,7 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
|
||||
|
||||
return items.map((item) => ({
|
||||
id: item.Id,
|
||||
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown",
|
||||
type: this.mapMediaReleaseType(item.Type),
|
||||
title: item.Name,
|
||||
subtitle: item.Taglines.at(0),
|
||||
description: item.Overview,
|
||||
@@ -179,6 +179,27 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
|
||||
}));
|
||||
}
|
||||
|
||||
private mapMediaReleaseType(type: string | undefined): MediaType {
|
||||
switch (type) {
|
||||
case "Audio":
|
||||
case "AudioBook":
|
||||
case "MusicAlbum":
|
||||
return "music";
|
||||
case "Book":
|
||||
return "book";
|
||||
case "Episode":
|
||||
case "Series":
|
||||
case "Season":
|
||||
return "tv";
|
||||
case "Movie":
|
||||
return "movie";
|
||||
case "Video":
|
||||
return "video";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// https://dev.emby.media/reference/RestAPI/UserService/getUsersPublic.html
|
||||
private async fetchUsersPublicAsync(): Promise<{ id: string; name: string }[]> {
|
||||
const apiKey = super.getSecretValue("apiKey");
|
||||
|
||||
+20
-20
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
|
||||
@@ -43,23 +42,24 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with GitHub Container Registry integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with GitHub Container Registry integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
|
||||
@@ -83,20 +83,20 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof RequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GithubIntegration" });
|
||||
@@ -43,38 +42,32 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Github integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Github integration`, {
|
||||
identifier,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.repos.listReleases({
|
||||
owner,
|
||||
repo: name,
|
||||
});
|
||||
const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name });
|
||||
|
||||
if (releasesResponse.data.length === 0) {
|
||||
localLogger.warn(`No releases found, for ${repository.identifier} with Github integration`, {
|
||||
identifier: repository.identifier,
|
||||
localLogger.warn(`No releases found, for ${owner}/${name} with Github integration`, {
|
||||
identifier: `${owner}/${name}`,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
@@ -90,20 +83,20 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof OctokitRequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitlabIntegration" });
|
||||
@@ -40,25 +39,20 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const api = this.getApi();
|
||||
|
||||
const details = await this.getDetailsAsync(api, repository.identifier);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.ProjectReleases.all(repository.identifier, {
|
||||
const releasesResponse = await api.ProjectReleases.all(identifier, {
|
||||
perPage: 100,
|
||||
});
|
||||
|
||||
if (releasesResponse instanceof Error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: releasesResponse.message,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
return { success: false, error: { code: "noReleasesFound" } };
|
||||
}
|
||||
|
||||
const releasesProviderResponse = releasesResponse.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
@@ -76,17 +70,19 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
const details = await this.getDetailsAsync(api, identifier);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: errorMessage,
|
||||
});
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export type {
|
||||
TdarrStatistics,
|
||||
TdarrWorker,
|
||||
} from "./interfaces/media-transcoding/media-transcoding-types";
|
||||
export type { ReleasesResponse } from "./interfaces/releases-providers/releases-providers-types";
|
||||
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
|
||||
export type { Notification } from "./interfaces/notifications/notification-types";
|
||||
|
||||
// Schemas
|
||||
|
||||
+8
-34
@@ -1,47 +1,21 @@
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "./releases-providers-types";
|
||||
import type { ReleaseProviderResponse, ReleaseResponse } from "./releases-providers-types";
|
||||
|
||||
export const getLatestRelease = (
|
||||
releases: ReleaseProviderResponse[],
|
||||
repository: ReleasesRepository,
|
||||
details?: DetailsProviderResponse,
|
||||
): ReleasesResponse => {
|
||||
versionRegex?: string,
|
||||
): ReleaseProviderResponse | null => {
|
||||
const validReleases = releases.filter((result) => {
|
||||
if (result.latestRelease) {
|
||||
return repository.versionRegex ? new RegExp(repository.versionRegex).test(result.latestRelease) : true;
|
||||
return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const latest =
|
||||
validReleases.length === 0
|
||||
? ({
|
||||
id: repository.id,
|
||||
error: { code: "noMatchingVersion" },
|
||||
} as ReleasesResponse)
|
||||
: validReleases.reduce(
|
||||
(latest, result) => {
|
||||
return {
|
||||
...details,
|
||||
...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest),
|
||||
id: repository.id,
|
||||
};
|
||||
},
|
||||
{
|
||||
id: "",
|
||||
latestRelease: "",
|
||||
latestReleaseAt: new Date(0),
|
||||
},
|
||||
);
|
||||
|
||||
return latest;
|
||||
return validReleases.length === 0
|
||||
? null
|
||||
: validReleases.reduce((latest, current) => (current.latestReleaseAt > latest.latestReleaseAt ? current : latest));
|
||||
};
|
||||
|
||||
export interface ReleasesProviderIntegration {
|
||||
getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse>;
|
||||
getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse>;
|
||||
}
|
||||
+10
-29
@@ -1,5 +1,11 @@
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
|
||||
export interface ReleasesRepository extends Record<string, unknown> {
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
|
||||
export interface DetailsProviderResponse {
|
||||
projectUrl?: string;
|
||||
projectDescription?: string;
|
||||
@@ -19,35 +25,10 @@ export interface ReleaseProviderResponse {
|
||||
isPreRelease?: boolean;
|
||||
}
|
||||
|
||||
export interface ReleasesRepository {
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
|
||||
type ReleasesErrorKeys = keyof TranslationObject["widget"]["releases"]["error"]["messages"];
|
||||
|
||||
export interface ReleasesResponse {
|
||||
id: string;
|
||||
latestRelease?: string;
|
||||
latestReleaseAt?: Date;
|
||||
releaseUrl?: string;
|
||||
releaseDescription?: string;
|
||||
isPreRelease?: boolean;
|
||||
projectUrl?: string;
|
||||
projectDescription?: string;
|
||||
isFork?: boolean;
|
||||
isArchived?: boolean;
|
||||
createdAt?: Date;
|
||||
starsCount?: number;
|
||||
openIssues?: number;
|
||||
forksCount?: number;
|
||||
export type ReleaseData = DetailsProviderResponse & ReleaseProviderResponse;
|
||||
|
||||
error?:
|
||||
| {
|
||||
code: ReleasesErrorKeys;
|
||||
}
|
||||
| {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
export type ReleaseError = { code: ReleasesErrorKeys } | { code: "unexpected"; message: string };
|
||||
|
||||
export type ReleaseResponse = { success: true; data: ReleaseData } | { success: false; error: ReleaseError };
|
||||
@@ -15,7 +15,7 @@ import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
|
||||
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease } from "../types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
|
||||
|
||||
@HandleIntegrationErrors([integrationAxiosHttpErrorHandler])
|
||||
export class JellyfinIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
|
||||
@@ -122,7 +122,7 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
|
||||
return result.data.map((item) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: item.Id!,
|
||||
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown",
|
||||
type: this.mapMediaReleaseType(item.Type),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
title: item.Name!,
|
||||
subtitle: item.Taglines?.at(0),
|
||||
@@ -140,6 +140,27 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
|
||||
}));
|
||||
}
|
||||
|
||||
private mapMediaReleaseType(type: BaseItemKind | undefined): MediaType {
|
||||
switch (type) {
|
||||
case "Audio":
|
||||
case "AudioBook":
|
||||
case "MusicAlbum":
|
||||
return "music";
|
||||
case "Book":
|
||||
return "book";
|
||||
case "Episode":
|
||||
case "Series":
|
||||
case "Season":
|
||||
return "tv";
|
||||
case "Movie":
|
||||
return "movie";
|
||||
case "Video":
|
||||
return "video";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ApiClient synchronously with an ApiKey or asynchronously
|
||||
* with a username and password.
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./linuxserverio-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
|
||||
@@ -24,56 +24,44 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with LinuxServerIO integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string): Promise<ReleaseResponse> {
|
||||
const { name } = this.parseIdentifier(identifier) ?? {};
|
||||
if (!name) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images"));
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
|
||||
if (!release) {
|
||||
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
|
||||
owner,
|
||||
name,
|
||||
});
|
||||
return { success: false, error: { code: "unexpected", message: error.message } };
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
|
||||
if (!release) {
|
||||
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
|
||||
name,
|
||||
});
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
latestRelease: release.version,
|
||||
latestReleaseAt: release.version_timestamp,
|
||||
releaseDescription: release.changelog?.shift()?.desc,
|
||||
@@ -82,7 +70,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
|
||||
isArchived: release.deprecated,
|
||||
createdAt: release.initial_date ? new Date(release.initial_date) : undefined,
|
||||
starsCount: release.stars,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { TestConnectionError } from "../base/test-connection/test-connection-err
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./npm-schemas";
|
||||
|
||||
export class NPMIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
@@ -22,35 +22,35 @@ export class NPMIntegration extends Integration implements ReleasesProviderInteg
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(
|
||||
this.url(`/${encodeURIComponent(repository.identifier)}`),
|
||||
);
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
if (!identifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url(`/${encodeURIComponent(identifier)}`));
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const formattedReleases = data.time.map((tag) => ({
|
||||
...tag,
|
||||
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
|
||||
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
|
||||
}));
|
||||
return getLatestRelease(formattedReleases, repository);
|
||||
}
|
||||
|
||||
const formattedReleases = data.time.map((tag) => ({
|
||||
...tag,
|
||||
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
|
||||
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
|
||||
}));
|
||||
|
||||
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return { success: true, data: latestRelease };
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./quay-schemas";
|
||||
|
||||
@@ -43,20 +42,22 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Quay integration`, {
|
||||
identifier,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
@@ -68,42 +69,29 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const details = {
|
||||
projectDescription: data.description,
|
||||
};
|
||||
|
||||
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
|
||||
if (!tag.name || !tag.last_modified) return acc;
|
||||
|
||||
acc.push({
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: new Date(tag.last_modified),
|
||||
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
return { success: false, error: { code: "unexpected", message: error.message } };
|
||||
}
|
||||
|
||||
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
|
||||
if (!tag.name || !tag.last_modified) return acc;
|
||||
acc.push({
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: new Date(tag.last_modified),
|
||||
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return { success: true, data: { projectDescription: data.description, ...latestRelease } };
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,19 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import type { IntegrationKindByCategory } from "@homarr/definitions";
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import type { ReleasesResponse } from "@homarr/integrations";
|
||||
import type { ReleaseResponse, ReleasesRepository } from "@homarr/integrations";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
|
||||
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||
|
||||
export const releasesRequestHandler = createCachedIntegrationRequestHandler<
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
IntegrationKindByCategory<"releasesProvider">,
|
||||
{
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
ReleasesRepository
|
||||
>({
|
||||
async requestAsync(integration, input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
const response = await integrationInstance.getLatestMatchingReleaseAsync({
|
||||
id: input.id,
|
||||
identifier: input.identifier,
|
||||
versionRegex: input.versionRegex,
|
||||
});
|
||||
|
||||
return {
|
||||
...response,
|
||||
integration: {
|
||||
name: integration.name,
|
||||
iconUrl: getIconUrl(integration.kind),
|
||||
},
|
||||
};
|
||||
requestAsync: async (integration, input) => {
|
||||
const instance = await createIntegrationAsync(integration);
|
||||
return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex);
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
queryKey: "repositoriesReleases",
|
||||
|
||||
@@ -21,6 +21,7 @@ import ReactMarkdown from "react-markdown";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { isDateWithin, isNullOrWhitespace, splitToChunksWithNItems } from "@homarr/common";
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
|
||||
@@ -96,55 +97,33 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
const repositories = useMemo(() => {
|
||||
const formattedResults = options.repositories
|
||||
.map((repository) => {
|
||||
if (repository.providerIntegrationId === undefined) {
|
||||
return {
|
||||
...repository,
|
||||
isNewRelease: false,
|
||||
isStaleRelease: false,
|
||||
latestReleaseAt: undefined,
|
||||
error: {
|
||||
code: "noProviderSeleceted",
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!repository.providerIntegrationId) return { ...repository, error: { code: "noProviderSeleceted" } };
|
||||
|
||||
const response = results.flat().find(({ data }) => data.id === repository.id)?.data;
|
||||
const repositoryResult = results.flat().find(({ id }) => id === repository.id);
|
||||
if (!repositoryResult) return { ...repository, error: { code: "noProviderResponse" } };
|
||||
if (!repositoryResult.success) return { ...repository, error: repositoryResult.error };
|
||||
|
||||
if (response === undefined)
|
||||
return {
|
||||
...repository,
|
||||
isNewRelease: false,
|
||||
isStaleRelease: false,
|
||||
latestReleaseAt: undefined,
|
||||
error: {
|
||||
code: "noProviderResponse",
|
||||
},
|
||||
};
|
||||
const { data: release, integration } = repositoryResult;
|
||||
|
||||
const isReleaseWithin = (relativeDate: string) =>
|
||||
Boolean(relativeDate) && isDateWithin(release.latestReleaseAt, relativeDate);
|
||||
|
||||
return {
|
||||
...repository,
|
||||
...response,
|
||||
isNewRelease:
|
||||
relativeDateOptions.newReleaseWithin !== "" && response.latestReleaseAt
|
||||
? isDateWithin(response.latestReleaseAt, relativeDateOptions.newReleaseWithin)
|
||||
: false,
|
||||
isStaleRelease:
|
||||
relativeDateOptions.staleReleaseWithin !== "" && response.latestReleaseAt
|
||||
? !isDateWithin(response.latestReleaseAt, relativeDateOptions.staleReleaseWithin)
|
||||
: false,
|
||||
viewed: releasesViewedList[repository.id] === response.latestRelease,
|
||||
...release,
|
||||
integration: { name: integration.name, iconUrl: getIconUrl(integration.kind) },
|
||||
isNewRelease: isReleaseWithin(relativeDateOptions.newReleaseWithin),
|
||||
isStaleRelease: !isReleaseWithin(relativeDateOptions.staleReleaseWithin),
|
||||
viewed: releasesViewedList[repository.id] === release.latestRelease,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(repository) =>
|
||||
repository.error !== undefined ||
|
||||
!options.showOnlyHighlighted ||
|
||||
repository.isNewRelease ||
|
||||
repository.isStaleRelease,
|
||||
"error" in repository || !options.showOnlyHighlighted || repository.isNewRelease || repository.isStaleRelease,
|
||||
)
|
||||
.sort((repoA, repoB) => {
|
||||
if (repoA.latestReleaseAt === undefined) return -1;
|
||||
if (repoB.latestReleaseAt === undefined) return 1;
|
||||
if ("error" in repoA) return -1;
|
||||
if ("error" in repoB) return 1;
|
||||
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
|
||||
}) as ReleasesRepositoryResponse[];
|
||||
|
||||
|
||||
Generated
+97
-97
@@ -245,17 +245,17 @@ importers:
|
||||
specifier: ^5.90.2
|
||||
version: 5.90.2(@tanstack/react-query@5.90.5(react@19.2.0))(next@15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0)
|
||||
'@trpc/client':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/next':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(next@15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(next@15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
'@trpc/react-query':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
'@trpc/server':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(typescript@5.9.3)
|
||||
'@xterm/addon-canvas':
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0(@xterm/xterm@5.5.0)
|
||||
@@ -636,17 +636,17 @@ importers:
|
||||
specifier: ^5.90.5
|
||||
version: 5.90.5(react@19.2.0)
|
||||
'@trpc/client':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/react-query':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
'@trpc/server':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(typescript@5.9.3)
|
||||
'@trpc/tanstack-react-query':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
lodash.clonedeep:
|
||||
specifier: ^4.5.0
|
||||
version: 4.5.0
|
||||
@@ -664,7 +664,7 @@ importers:
|
||||
version: 2.2.3
|
||||
trpc-to-openapi:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.6.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.12))(zod@4.1.12)
|
||||
version: 3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.7.1(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.12))(zod@4.1.12)
|
||||
zod:
|
||||
specifier: ^4.1.12
|
||||
version: 4.1.12
|
||||
@@ -691,11 +691,11 @@ importers:
|
||||
packages/auth:
|
||||
dependencies:
|
||||
'@auth/core':
|
||||
specifier: ^0.41.0
|
||||
version: 0.41.0
|
||||
specifier: ^0.41.1
|
||||
version: 0.41.1
|
||||
'@auth/drizzle-adapter':
|
||||
specifier: ^1.11.0
|
||||
version: 1.11.0
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.1
|
||||
'@homarr/certificates':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../certificates
|
||||
@@ -730,8 +730,8 @@ importers:
|
||||
specifier: 15.5.6
|
||||
version: 15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2)
|
||||
next-auth:
|
||||
specifier: 5.0.0-beta.29
|
||||
version: 5.0.0-beta.29(next@15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0)
|
||||
specifier: 5.0.0-beta.30
|
||||
version: 5.0.0-beta.30(next@15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0)
|
||||
react:
|
||||
specifier: 19.2.0
|
||||
version: 19.2.0
|
||||
@@ -964,14 +964,14 @@ importers:
|
||||
specifier: ^5.90.5
|
||||
version: 5.90.5(react@19.2.0)
|
||||
'@trpc/client':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(typescript@5.9.3)
|
||||
'@trpc/tanstack-react-query':
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
specifier: ^11.7.0
|
||||
version: 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
node-cron:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
@@ -1130,8 +1130,8 @@ importers:
|
||||
packages/db:
|
||||
dependencies:
|
||||
'@auth/core':
|
||||
specifier: ^0.41.0
|
||||
version: 0.41.0
|
||||
specifier: ^0.41.1
|
||||
version: 0.41.1
|
||||
'@homarr/common':
|
||||
specifier: workspace:^0.1.0
|
||||
version: link:../common
|
||||
@@ -2493,20 +2493,6 @@ packages:
|
||||
'@asamuzakjp/nwsapi@2.3.9':
|
||||
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
|
||||
|
||||
'@auth/core@0.40.0':
|
||||
resolution: {integrity: sha512-n53uJE0RH5SqZ7N1xZoMKekbHfQgjd0sAEyUbE+IYJnmuQkbvuZnXItCU7d+i7Fj8VGOgqvNO7Mw4YfBTlZeQw==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
nodemailer: ^6.8.0
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
'@simplewebauthn/server':
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
|
||||
'@auth/core@0.41.0':
|
||||
resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==}
|
||||
peerDependencies:
|
||||
@@ -2521,8 +2507,22 @@ packages:
|
||||
nodemailer:
|
||||
optional: true
|
||||
|
||||
'@auth/drizzle-adapter@1.11.0':
|
||||
resolution: {integrity: sha512-7YHi5bTZZs4+D6TQIT/AWgZ2PLL7Zc9fS7bteHKMrsEMemarbdv3H5eF8RfF1FpRtyK8MAj4EQV4BWf0gCzAYw==}
|
||||
'@auth/core@0.41.1':
|
||||
resolution: {integrity: sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
nodemailer: ^7.0.7
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
'@simplewebauthn/server':
|
||||
optional: true
|
||||
nodemailer:
|
||||
optional: true
|
||||
|
||||
'@auth/drizzle-adapter@1.11.1':
|
||||
resolution: {integrity: sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA==}
|
||||
|
||||
'@axiomhq/js@1.0.0-rc.3':
|
||||
resolution: {integrity: sha512-Zm10TczcMLounWqC42nMkXQ7XKLqjzLrd5ia022oBKDUZqAFVg2y9d1quQVNV4FlXyg9MKDdfMjpKQRmzEGaog==}
|
||||
@@ -4413,19 +4413,19 @@ packages:
|
||||
tree-sitter:
|
||||
optional: true
|
||||
|
||||
'@trpc/client@11.6.0':
|
||||
resolution: {integrity: sha512-DyWbYk2hd50BaVrXWVkaUnaSwgAF5g/lfBkXtkF1Aqlk6BtSzGUo3owPkgqQO2I5LwWy1+ra9TsSfBBvIZpTwg==}
|
||||
'@trpc/client@11.7.1':
|
||||
resolution: {integrity: sha512-uOnAjElKI892/U6aQMcBHYs3x7mme3Cvv1F87ytBL56rBvs7+DyK7r43zgaXKf13+GtPEI6ex5xjVUfyDW8XcQ==}
|
||||
peerDependencies:
|
||||
'@trpc/server': 11.6.0
|
||||
'@trpc/server': 11.7.1
|
||||
typescript: '>=5.7.2'
|
||||
|
||||
'@trpc/next@11.6.0':
|
||||
resolution: {integrity: sha512-LVzXQ4f3pyOJDMA0qXaWkTnnJtnRgzDvW5c1SPuragI69r9mG+Qs0+SgeekV/buSqUy35wMYVvwuEB/DfKvODw==}
|
||||
'@trpc/next@11.7.1':
|
||||
resolution: {integrity: sha512-LIoZk9z6tsPCcb7Iijc7usPuuxKm+UuwhU9jksc6TNk5AbDTUvEM/M+OtE4zABN4T3U/JFagn3Wt8s6vDEhNGw==}
|
||||
peerDependencies:
|
||||
'@tanstack/react-query': ^5.59.15
|
||||
'@trpc/client': 11.6.0
|
||||
'@trpc/react-query': 11.6.0
|
||||
'@trpc/server': 11.6.0
|
||||
'@trpc/client': 11.7.1
|
||||
'@trpc/react-query': 11.7.1
|
||||
'@trpc/server': 11.7.1
|
||||
next: '*'
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
@@ -4436,27 +4436,27 @@ packages:
|
||||
'@trpc/react-query':
|
||||
optional: true
|
||||
|
||||
'@trpc/react-query@11.6.0':
|
||||
resolution: {integrity: sha512-xljUCzROa23cC89SEd5fwbKiWrGus2NDwtg8zszPlsFvaByWW50Jx6y5sLPXhp/g1FBsEtCInNNhEEL0UCHwGw==}
|
||||
'@trpc/react-query@11.7.1':
|
||||
resolution: {integrity: sha512-dEHDjIqSTzO8nLlCbtiFBMBwhbSkK1QP7aYVo3nP3sYBna0b+iCtrPXdxVPCSopr9/aIqDTEh+dMRZa7yBgjfQ==}
|
||||
peerDependencies:
|
||||
'@tanstack/react-query': ^5.80.3
|
||||
'@trpc/client': 11.6.0
|
||||
'@trpc/server': 11.6.0
|
||||
'@trpc/client': 11.7.1
|
||||
'@trpc/server': 11.7.1
|
||||
react: '>=18.2.0'
|
||||
react-dom: '>=18.2.0'
|
||||
typescript: '>=5.7.2'
|
||||
|
||||
'@trpc/server@11.6.0':
|
||||
resolution: {integrity: sha512-skTso0AWbOZck40jwNeYv++AMZXNWLUWdyk+pB5iVaYmEKTuEeMoPrEudR12VafbEU6tZa8HK3QhBfTYYHDCdg==}
|
||||
'@trpc/server@11.7.1':
|
||||
resolution: {integrity: sha512-N3U8LNLIP4g9C7LJ/sLkjuPHwqlvE3bnspzC4DEFVdvx2+usbn70P80E3wj5cjOTLhmhRiwJCSXhlB+MHfGeCw==}
|
||||
peerDependencies:
|
||||
typescript: '>=5.7.2'
|
||||
|
||||
'@trpc/tanstack-react-query@11.6.0':
|
||||
resolution: {integrity: sha512-254OpGV2RT9Djl6HRJmokDMT4pZz3beLs+8/T35ySfBxiC5OdEsOeESIJpC302YaZcCGwkOZ+d+FKFXgpy0YtQ==}
|
||||
'@trpc/tanstack-react-query@11.7.1':
|
||||
resolution: {integrity: sha512-qc7kz4NY7CCvCxLy5HGptfKd3e3yJnWmTd6/Gkr4IY8B73PNFmcHKvLWE4kzU7r+R72MfT57TXrCEJ7ErLSMtw==}
|
||||
peerDependencies:
|
||||
'@tanstack/react-query': ^5.80.3
|
||||
'@trpc/client': 11.6.0
|
||||
'@trpc/server': 11.6.0
|
||||
'@trpc/client': 11.7.1
|
||||
'@trpc/server': 11.7.1
|
||||
react: '>=18.2.0'
|
||||
react-dom: '>=18.2.0'
|
||||
typescript: '>=5.7.2'
|
||||
@@ -8108,14 +8108,14 @@ packages:
|
||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
next-auth@5.0.0-beta.29:
|
||||
resolution: {integrity: sha512-Ukpnuk3NMc/LiOl32njZPySk7pABEzbjhMUFd5/n10I0ZNC7NCuVv8IY2JgbDek2t/PUOifQEoUiOOTLy4os5A==}
|
||||
next-auth@5.0.0-beta.30:
|
||||
resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==}
|
||||
peerDependencies:
|
||||
'@simplewebauthn/browser': ^9.0.1
|
||||
'@simplewebauthn/server': ^9.0.2
|
||||
next: ^14.0.0-0 || ^15.0.0-0
|
||||
nodemailer: ^6.6.5
|
||||
react: ^18.2.0 || ^19.0.0-0
|
||||
next: ^14.0.0-0 || ^15.0.0 || ^16.0.0
|
||||
nodemailer: ^7.0.7
|
||||
react: ^18.2.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@simplewebauthn/browser':
|
||||
optional: true
|
||||
@@ -10950,14 +10950,6 @@ snapshots:
|
||||
|
||||
'@asamuzakjp/nwsapi@2.3.9': {}
|
||||
|
||||
'@auth/core@0.40.0':
|
||||
dependencies:
|
||||
'@panva/hkdf': 1.2.1
|
||||
jose: 6.0.8
|
||||
oauth4webapi: 3.3.0
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
|
||||
'@auth/core@0.41.0':
|
||||
dependencies:
|
||||
'@panva/hkdf': 1.2.1
|
||||
@@ -10966,9 +10958,17 @@ snapshots:
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
|
||||
'@auth/drizzle-adapter@1.11.0':
|
||||
'@auth/core@0.41.1':
|
||||
dependencies:
|
||||
'@auth/core': 0.41.0
|
||||
'@panva/hkdf': 1.2.1
|
||||
jose: 6.0.8
|
||||
oauth4webapi: 3.3.0
|
||||
preact: 10.24.3
|
||||
preact-render-to-string: 6.5.11(preact@10.24.3)
|
||||
|
||||
'@auth/drizzle-adapter@1.11.1':
|
||||
dependencies:
|
||||
'@auth/core': 0.41.1
|
||||
transitivePeerDependencies:
|
||||
- '@simplewebauthn/browser'
|
||||
- '@simplewebauthn/server'
|
||||
@@ -13181,41 +13181,41 @@ snapshots:
|
||||
tree-sitter: 0.22.4
|
||||
optional: true
|
||||
|
||||
'@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)':
|
||||
'@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@trpc/server': 11.6.0(typescript@5.9.3)
|
||||
'@trpc/server': 11.7.1(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
'@trpc/next@11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(next@15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
'@trpc/next@11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(next@15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@trpc/client': 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.6.0(typescript@5.9.3)
|
||||
'@trpc/client': 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.7.1(typescript@5.9.3)
|
||||
next: 15.5.6(@babel/core@7.26.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
typescript: 5.9.3
|
||||
optionalDependencies:
|
||||
'@tanstack/react-query': 5.90.5(react@19.2.0)
|
||||
'@trpc/react-query': 11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
'@trpc/react-query': 11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)
|
||||
|
||||
'@trpc/react-query@11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
'@trpc/react-query@11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@tanstack/react-query': 5.90.5(react@19.2.0)
|
||||
'@trpc/client': 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.6.0(typescript@5.9.3)
|
||||
'@trpc/client': 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.7.1(typescript@5.9.3)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
typescript: 5.9.3
|
||||
|
||||
'@trpc/server@11.6.0(typescript@5.9.3)':
|
||||
'@trpc/server@11.7.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@trpc/tanstack-react-query@11.6.0(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.6.0(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
'@trpc/tanstack-react-query@11.7.1(@tanstack/react-query@5.90.5(react@19.2.0))(@trpc/client@11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.7.1(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@tanstack/react-query': 5.90.5(react@19.2.0)
|
||||
'@trpc/client': 11.6.0(@trpc/server@11.6.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.6.0(typescript@5.9.3)
|
||||
'@trpc/client': 11.7.1(@trpc/server@11.7.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.7.1(typescript@5.9.3)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
typescript: 5.9.3
|
||||
@@ -17438,9 +17438,9 @@ snapshots:
|
||||
|
||||
netmask@2.0.2: {}
|
||||
|
||||
next-auth@5.0.0-beta.29(next@15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0):
|
||||
next-auth@5.0.0-beta.30(next@15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2))(react@19.2.0):
|
||||
dependencies:
|
||||
'@auth/core': 0.40.0
|
||||
'@auth/core': 0.41.0
|
||||
next: 15.5.6(@babel/core@7.28.4)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.2)
|
||||
react: 19.2.0
|
||||
|
||||
@@ -19724,9 +19724,9 @@ snapshots:
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
||||
trpc-to-openapi@3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.6.0(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.12))(zod@4.1.12):
|
||||
trpc-to-openapi@3.1.0(patch_hash=2ca3c16af0fcca0c736697ad4fe553a14f794524fa9ce0d5c3e8ee4aea76090c)(@trpc/server@11.7.1(typescript@5.9.3))(zod-openapi@5.3.0(zod@4.1.12))(zod@4.1.12):
|
||||
dependencies:
|
||||
'@trpc/server': 11.6.0(typescript@5.9.3)
|
||||
'@trpc/server': 11.7.1(typescript@5.9.3)
|
||||
co-body: 6.2.0
|
||||
h3: 1.15.1
|
||||
openapi3-ts: 4.4.0
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"VinnyVynce","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47Line truncated
|
||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"VinnyVynce","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47Line truncated
|
||||
Reference in new issue
Block a user