Skip to content

Commit ec69ce0

Browse files
fix(packages): handle duplicated packages
1 parent b923d50 commit ec69ce0

File tree

6 files changed

+112
-54
lines changed

6 files changed

+112
-54
lines changed

src/lib/array.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* A utility function to only keep unique items in
3+
* an array, based on the uniqTransform parameter.
4+
*
5+
* @param arr the input array
6+
* @param uniqTransform the transformation function
7+
* to make items unique
8+
* @returns the filtered array, containing only unique items
9+
*
10+
* @see {@link https://stackoverflow.com/a/70503699/12070367|Original implementation}
11+
*/
12+
export function uniq<T, U>(arr: T[], uniqTransform: (item: T) => U) {
13+
const track = new Set<U>();
14+
return arr.filter(item => {
15+
const value = uniqTransform(item);
16+
return track.has(value) ? false : track.add(value);
17+
});
18+
}

src/lib/repositories.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Category, Entries, Prettify, RepoInfo } from "$lib/types";
2+
import { uniq } from "$lib/array";
23

34
export const repos: Record<Category, { name: string; repos: RepoInfo[] }> = {
45
svelte: {
@@ -151,25 +152,6 @@ export const publicRepos: Repository[] = iterableRepos.flatMap(([slug, { name, r
151152
}))
152153
);
153154

154-
/**
155-
* A utility function to only keep unique items in
156-
* an array, based on the uniqTransform parameter.
157-
*
158-
* @param arr the input array
159-
* @param uniqTransform the transformation function
160-
* to make items unique
161-
* @returns the filtered array, containing only unique items
162-
*
163-
* @see {@link https://stackoverflow.com/a/70503699/12070367|Original implementation}
164-
*/
165-
function uniq<T, U>(arr: T[], uniqTransform: (item: T) => U) {
166-
const track = new Set<U>();
167-
return arr.filter(item => {
168-
const value = uniqTransform(item);
169-
return track.has(value) ? false : track.add(value);
170-
});
171-
}
172-
173155
/**
174156
* Return a unique array of owner and name of
175157
* the available repositories

src/lib/server/package-discoverer.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export class PackageDiscoverer {
4343
})
4444
)
4545
];
46+
console.log(
47+
`Discovered ${packages.length} packages for ${repo.owner}/${repo.repoName}: ${packages.join(", ")}`
48+
);
4649
return { ...repo, packages };
4750
})
4851
);
@@ -79,8 +82,8 @@ export class PackageDiscoverer {
7982
}));
8083

8184
for (const [i, item] of acc.entries()) {
82-
if (item.category.slug === category.slug && acc[i]) {
83-
acc[i].packages.push(...formattedPackages);
85+
if (item.category.slug === category.slug) {
86+
acc[i]?.packages.push(...formattedPackages);
8487
return acc;
8588
}
8689
}

src/routes/+layout.server.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { discoverer } from "$lib/server/package-discoverer";
2+
import { uniq } from "$lib/array";
23

34
export async function load() {
45
const categorizedPackages = await discoverer.getOrDiscoverCategorized();
@@ -7,9 +8,12 @@ export async function load() {
78
// The displayable data, available to load from clients
89
displayablePackages: categorizedPackages.map(res => ({
910
...res,
10-
packages: res.packages
11-
.map(({ dataFilter, metadataFromTag, changelogContentsReplacer, ...rest }) => rest)
12-
.toSorted((a, b) => a.packageName.localeCompare(b.packageName))
11+
packages: uniq(
12+
res.packages
13+
.map(({ dataFilter, metadataFromTag, changelogContentsReplacer, ...rest }) => rest)
14+
.toSorted((a, b) => a.packageName.localeCompare(b.packageName)),
15+
item => item.packageName
16+
)
1317
}))
1418
};
1519
}
Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,90 @@
11
import semver from "semver";
22
import { error } from "@sveltejs/kit";
3-
import { gitHubCache } from "$lib/server/github-cache";
3+
import { gitHubCache, type GitHubRelease } from "$lib/server/github-cache";
44
import { discoverer } from "$lib/server/package-discoverer";
55

66
export async function load({ params }) {
77
const { package: slugPackage } = params;
88
const categorizedPackages = await discoverer.getOrDiscoverCategorized();
99

10-
// Discover packages, if this one doesn't exist, return 404
10+
let currentPackage:
11+
| (Omit<
12+
(typeof categorizedPackages)[number]["packages"][number],
13+
"dataFilter" | "metadataFromTag" | "changelogContentsReplacer"
14+
> & { category: (typeof categorizedPackages)[number]["category"] })
15+
| undefined = undefined;
16+
const foundVersions = new Set<string>();
17+
const releases: (GitHubRelease & { cleanVersion: string })[] = [];
18+
19+
// Discover releases
20+
console.log("Starting loading releases...");
1121
for (const { category, packages } of categorizedPackages) {
12-
for (const fullPackage of packages) {
13-
const { packageName, ...repo } = fullPackage;
14-
const { dataFilter, metadataFromTag, changelogContentsReplacer, ...rest } = repo;
22+
for (const { packageName, ...repo } of packages) {
1523
if (packageName.toLowerCase() === slugPackage.toLowerCase()) {
16-
return {
17-
currentPackage: {
18-
category,
19-
packageName,
20-
...rest
21-
},
22-
releases: gitHubCache.getReleases({ ...repo, category }).then(releases => {
23-
const dataFiltered = releases
24-
.filter(release => dataFilter?.(release) ?? true)
25-
.sort((a, b) => {
26-
const [, firstVersion] = repo.metadataFromTag(a.tag_name);
27-
const [, secondVersion] = repo.metadataFromTag(b.tag_name);
28-
return semver.rcompare(firstVersion, secondVersion);
29-
});
30-
const pkgTagFiltered = dataFiltered.filter(({ tag_name }) =>
31-
tag_name.includes(slugPackage)
24+
// 1. Get releases
25+
const cachedReleases = await gitHubCache.getReleases({ ...repo, category });
26+
console.log(
27+
`${cachedReleases.length} releases found for repo ${repo.owner}/${repo.repoName}`
28+
);
29+
30+
// 2. Filter out invalid ones
31+
const dataFiltered = cachedReleases
32+
.filter(release => repo.dataFilter?.(release) ?? true)
33+
.sort((a, b) => {
34+
const [, firstVersion] = repo.metadataFromTag(a.tag_name);
35+
const [, secondVersion] = repo.metadataFromTag(b.tag_name);
36+
return semver.rcompare(firstVersion, secondVersion);
37+
});
38+
console.log("Length after filtering:", dataFiltered.length);
39+
const pkgTagFiltered = dataFiltered.filter(({ tag_name }) =>
40+
tag_name.toLowerCase().includes(slugPackage.toLowerCase())
41+
);
42+
console.log(`Got ${pkgTagFiltered.length} releases after filtering by package name`);
43+
// Get the releases matching the slug, or all of them if none
44+
// (the latter case for repos with no package in names)
45+
const validReleases = pkgTagFiltered.length ? pkgTagFiltered : dataFiltered;
46+
console.log("Final filtered count:", validReleases.length);
47+
48+
// 3. For each release, check if it is already found, searching by versions
49+
const { dataFilter, metadataFromTag, changelogContentsReplacer, ...rest } = repo;
50+
for (const release of validReleases) {
51+
const [, cleanVersion] = repo.metadataFromTag(release.tag_name);
52+
console.log(`Release ${release.tag_name}, extracted version: ${cleanVersion}`);
53+
if (foundVersions.has(cleanVersion)) continue;
54+
55+
// If not, add its version to the set and itself to the final version
56+
const currentNewestVersion = [...foundVersions].sort(semver.rcompare)[0];
57+
console.log("Current newest version", currentNewestVersion);
58+
foundVersions.add(cleanVersion);
59+
releases.push({ ...release, cleanVersion });
60+
61+
// If it is newer than the newest we got, set this repo as the "final repo"
62+
if (!currentNewestVersion || semver.gt(cleanVersion, currentNewestVersion)) {
63+
console.log(
64+
`Current newest version "${currentNewestVersion}" doesn't exist or is lesser than ${cleanVersion}, setting ${rest.owner}/${rest.repoName} as final repo`
3265
);
33-
return pkgTagFiltered.length ? pkgTagFiltered : dataFiltered;
34-
})
35-
};
66+
currentPackage = {
67+
category,
68+
packageName,
69+
...rest
70+
};
71+
}
72+
}
73+
console.log("Done");
3674
}
3775
}
3876
}
3977

78+
if (currentPackage) {
79+
// Return the final sorted results and filter back out the clean version
80+
return {
81+
currentPackage,
82+
releases: releases
83+
.toSorted((a, b) => semver.rcompare(a.cleanVersion, b.cleanVersion))
84+
.filter(({ cleanVersion, ...release }) => release)
85+
};
86+
}
87+
88+
// If this one doesn't exist, return 404
4089
error(404);
4190
}

src/routes/package/[...package]/+page.svelte

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { navigating } from "$app/state";
23
import { LoaderCircle } from "@lucide/svelte";
34
import ReleaseCard from "./ReleaseCard.svelte";
45
import SidePanel from "./SidePanel.svelte";
@@ -7,30 +8,31 @@
78
let { data } = $props();
89
</script>
910

10-
{#await data.releases}
11+
{#if navigating.to}
1112
<span class="inline-flex items-center justify-center">
1213
<LoaderCircle class="mr-2 size-4 animate-spin" />
1314
Loading...
1415
</span>
15-
{:then releases}
16-
<div class="my-8">
16+
{:else}
17+
<div class="my-8 *:last:mt-4">
1718
<h1 class="text-5xl font-semibold text-primary">{data.currentPackage.packageName}</h1>
1819
<h2 class="text-xl text-muted-foreground">
1920
{data.currentPackage.owner}/{data.currentPackage.repoName}
2021
</h2>
22+
<h3 class="italic">*insert description*</h3>
2123
</div>
2224
<div class="flex gap-8">
2325
<Accordion.Root
2426
type="multiple"
25-
value={releases
27+
value={data.releases
2628
// Only expand releases that are less than a week old
2729
.filter(({ created_at }) => {
2830
return new Date(created_at).getTime() > new Date().getTime() - 1000 * 60 * 60 * 24 * 7;
2931
})
3032
.map(({ id }) => id.toString())}
3133
class="w-full"
3234
>
33-
{#each releases as release (release.id)}
35+
{#each data.releases as release (release.id)}
3436
<ReleaseCard
3537
packageName={data.currentPackage.packageName}
3638
repo={{ owner: data.currentPackage.owner, name: data.currentPackage.repoName }}
@@ -44,4 +46,4 @@
4446
class="h-fit w-2/5"
4547
/>
4648
</div>
47-
{/await}
49+
{/if}

0 commit comments

Comments
 (0)