Skip to content

Commit 1fb8307

Browse files
feat: feature scaffolding
1 parent 4fbee1f commit 1fb8307

File tree

3 files changed

+152
-73
lines changed

3 files changed

+152
-73
lines changed

src/routes/package/[...package]/+page.server.ts

Lines changed: 100 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,90 +3,123 @@ import semver from "semver";
33
import { gitHubCache, type GitHubRelease } from "$lib/server/github-cache";
44
import { discoverer } from "$lib/server/package-discoverer";
55
import type { Repository } from "$lib/repositories";
6+
import type { Prettify } from "$lib/types";
67

7-
export async function load({ params }) {
8-
const { package: slugPackage } = params;
9-
const categorizedPackages = await discoverer.getOrDiscoverCategorized();
10-
8+
async function getReleases(
9+
packageName: string,
10+
allPackages: Awaited<ReturnType<typeof discoverer.getOrDiscoverCategorized>>
11+
) {
1112
let currentPackage:
12-
| (Omit<Repository, "dataFilter" | "metadataFromTag" | "changelogContentsReplacer"> &
13-
Pick<(typeof categorizedPackages)[number]["packages"][number], "pkg">)
13+
| Prettify<
14+
Omit<Repository, "dataFilter" | "metadataFromTag" | "changelogContentsReplacer"> &
15+
Pick<(typeof allPackages)[number]["packages"][number], "pkg">
16+
>
1417
| undefined = undefined;
1518
const foundVersions = new Set<string>();
1619
const releases: ({ cleanName: string; cleanVersion: string } & GitHubRelease)[] = [];
1720

1821
// Discover releases
1922
console.log("Starting loading releases...");
20-
for (const { category, packages } of categorizedPackages) {
23+
for (const { category, packages } of allPackages) {
2124
for (const { pkg, ...repo } of packages) {
22-
if (pkg.name.toLowerCase() === slugPackage.toLowerCase()) {
23-
// 1. Get releases
24-
const cachedReleases = await gitHubCache.getReleases({ ...repo, category });
25-
console.log(
26-
`${cachedReleases.length} releases found for repo ${repo.owner}/${repo.repoName}`
27-
);
25+
if (pkg.name.localeCompare(packageName, undefined, { sensitivity: "base" }) !== 0) continue;
26+
27+
// 1. Get releases
28+
const cachedReleases = await gitHubCache.getReleases({ ...repo, category });
29+
console.log(
30+
`${cachedReleases.length} releases found for repo ${repo.owner}/${repo.repoName}`
31+
);
2832

29-
// 2. Filter out invalid ones
30-
const validReleases = cachedReleases
31-
.filter(release => {
32-
const [name] = repo.metadataFromTag(release.tag_name);
33-
return (
34-
(repo.dataFilter?.(release) ?? true) &&
35-
slugPackage.localeCompare(name, undefined, { sensitivity: "base" }) === 0
36-
);
37-
})
38-
.sort((a, b) => {
39-
const [, firstVersion] = repo.metadataFromTag(a.tag_name);
40-
const [, secondVersion] = repo.metadataFromTag(b.tag_name);
41-
return semver.rcompare(firstVersion, secondVersion);
42-
});
43-
console.log("Length after filtering:", validReleases.length);
44-
// Get the releases matching the slug, or all of them if none
45-
// (the latter case for repos with no package in names)
46-
console.log("Final filtered count:", validReleases.length);
33+
// 2. Filter out invalid ones
34+
const validReleases = cachedReleases
35+
.filter(release => {
36+
const [name] = repo.metadataFromTag(release.tag_name);
37+
return (
38+
(repo.dataFilter?.(release) ?? true) &&
39+
pkg.name.localeCompare(name, undefined, { sensitivity: "base" }) === 0
40+
);
41+
})
42+
.sort((a, b) => {
43+
const [, firstVersion] = repo.metadataFromTag(a.tag_name);
44+
const [, secondVersion] = repo.metadataFromTag(b.tag_name);
45+
return semver.rcompare(firstVersion, secondVersion);
46+
});
47+
console.log("Length after filtering:", validReleases.length);
48+
// Get the releases matching the slug, or all of them if none
49+
// (the latter case for repos with no package in names)
50+
console.log("Final filtered count:", validReleases.length);
4751

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 [cleanName, cleanVersion] = repo.metadataFromTag(release.tag_name);
52-
console.log(`Release ${release.tag_name}, extracted version: ${cleanVersion}`);
53-
if (foundVersions.has(cleanVersion)) continue;
52+
// 3. For each release, check if it is already found, searching by versions
53+
const { dataFilter, metadataFromTag, changelogContentsReplacer, ...rest } = repo;
54+
for (const release of validReleases) {
55+
const [cleanName, cleanVersion] = repo.metadataFromTag(release.tag_name);
56+
console.log(`Release ${release.tag_name}, extracted version: ${cleanVersion}`);
57+
if (foundVersions.has(cleanVersion)) continue;
5458

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({ cleanName, cleanVersion, ...release });
59+
// If not, add its version to the set and itself to the final version
60+
const currentNewestVersion = [...foundVersions].sort(semver.rcompare)[0];
61+
console.log("Current newest version", currentNewestVersion);
62+
foundVersions.add(cleanVersion);
63+
releases.push({ cleanName, cleanVersion, ...release });
6064

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`
65-
);
66-
currentPackage = {
67-
category,
68-
pkg,
69-
...rest
70-
};
71-
}
65+
// If it is newer than the newest we got, set this repo as the "final repo"
66+
if (!currentNewestVersion || semver.gt(cleanVersion, currentNewestVersion)) {
67+
console.log(
68+
`Current newest version "${currentNewestVersion}" doesn't exist or is lesser than ${cleanVersion}, setting ${rest.owner}/${rest.repoName} as final repo`
69+
);
70+
currentPackage = {
71+
category,
72+
pkg,
73+
...rest
74+
};
7275
}
73-
console.log("Done");
7476
}
77+
console.log("Done");
7578
}
7679
}
7780

78-
if (currentPackage) {
79-
// Return the final sorted results
80-
return {
81-
currentPackage,
82-
releases: releases.toSorted(
83-
(a, b) =>
84-
new Date(b.published_at ?? b.created_at).getTime() -
85-
new Date(a.published_at ?? a.created_at).getTime()
86-
)
87-
};
88-
}
81+
return currentPackage
82+
? {
83+
releasesRepo: currentPackage,
84+
releases: releases.toSorted(
85+
(a, b) =>
86+
new Date(b.published_at ?? b.created_at).getTime() -
87+
new Date(a.published_at ?? a.created_at).getTime()
88+
)
89+
}
90+
: undefined;
91+
}
92+
93+
async function getAllOtherReleases(exceptPackage: string) {
94+
const discovery = await discoverer.getOrDiscoverCategorized();
95+
const otherPackages = discovery.flatMap(({ packages }) => packages);
96+
97+
const otherReleases = await Promise.all(
98+
otherPackages.map(async otherPackage => await getReleases(otherPackage.pkg.name, discovery))
99+
);
100+
101+
return otherReleases
102+
.filter(o => o !== undefined)
103+
.filter(
104+
({ releasesRepo: releasesPackage }) =>
105+
releasesPackage.pkg.name.localeCompare(exceptPackage, undefined, {
106+
sensitivity: "base"
107+
}) !== 0
108+
);
109+
}
110+
111+
export async function load({ params }) {
112+
const { package: slugPackage } = params;
113+
const categorizedPackages = await discoverer.getOrDiscoverCategorized();
114+
115+
const computedReleases = await getReleases(slugPackage, categorizedPackages);
116+
117+
if (!computedReleases) error(404);
89118

90-
// If this one doesn't exist, return 404
91-
error(404);
119+
const { releasesRepo: currentPackage, releases } = computedReleases;
120+
return {
121+
currentPackage,
122+
releases,
123+
otherReleases: getAllOtherReleases(currentPackage.pkg.name)
124+
};
92125
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
headless
8989
packageName={data.currentPackage.pkg.name}
9090
allPackages={data.displayablePackages}
91+
otherReleases={data.otherReleases}
9192
bind:showPrereleases
9293
class="my-8"
9394
/>
@@ -134,6 +135,7 @@
134135
<SidePanel
135136
packageName={data.currentPackage.pkg.name}
136137
allPackages={data.displayablePackages}
138+
otherReleases={data.otherReleases}
137139
class="hidden h-fit w-140 lg:block"
138140
bind:showPrereleases
139141
/>

src/routes/package/[...package]/SidePanel.svelte

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,41 @@
22
import type { ClassValue } from "svelte/elements";
33
import { page } from "$app/state";
44
import { ChevronRight } from "@lucide/svelte";
5+
import type { GitHubRelease } from "$lib/server/github-cache";
56
import type { CategorizedPackage } from "$lib/server/package-discoverer";
7+
import type { Prettify } from "$lib/types";
68
import { persisted } from "$lib/persisted.svelte";
79
import { cn } from "$lib/utils";
10+
import { Badge } from "$lib/components/ui/badge";
811
import { Checkbox } from "$lib/components/ui/checkbox";
912
import { Label } from "$lib/components/ui/label";
1013
import { Separator } from "$lib/components/ui/separator";
1114
import * as Card from "$lib/components/ui/card";
1215
16+
type MaybePromise<T> = Promise<T> | T;
17+
1318
type Props = {
1419
packageName?: string;
15-
allPackages?: (Omit<CategorizedPackage, "packages"> & {
16-
packages: Omit<
17-
CategorizedPackage["packages"][number],
18-
"dataFilter" | "metadataFromTag" | "changelogContentsReplacer"
19-
>[];
20-
})[];
20+
allPackages?: Prettify<
21+
Omit<CategorizedPackage, "packages"> & {
22+
packages: Omit<
23+
CategorizedPackage["packages"][number],
24+
"dataFilter" | "metadataFromTag" | "changelogContentsReplacer"
25+
>[];
26+
}
27+
>[];
28+
otherReleases?: MaybePromise<
29+
{
30+
releasesRepo: Prettify<
31+
Pick<CategorizedPackage, "category"> &
32+
Omit<
33+
CategorizedPackage["packages"][number],
34+
"dataFilter" | "metadataFromTag" | "changelogContentsReplacer"
35+
>
36+
>;
37+
releases: ({ cleanName: string; cleanVersion: string } & GitHubRelease)[];
38+
}[]
39+
>;
2140
showPrereleases?: boolean;
2241
headless?: boolean;
2342
class?: ClassValue;
@@ -26,17 +45,34 @@
2645
packageName = "",
2746
allPackages = [],
2847
showPrereleases = $bindable(true),
48+
otherReleases = [],
2949
headless = false,
3050
class: className
3151
}: Props = $props();
3252
let id = $props.id();
3353
54+
let awaitedOtherReleases = $state<Awaited<typeof otherReleases>>([]);
55+
$effect(() => {
56+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
57+
otherReleases;
58+
(async () => {
59+
awaitedOtherReleases = await otherReleases;
60+
})();
61+
});
62+
let isExpanded = $state(false);
63+
3464
let storedPrereleaseState = persisted(`show-${packageName}-prereleases`, showPrereleases);
3565
$effect(() => {
3666
storedPrereleaseState.value = showPrereleases;
3767
});
3868
</script>
3969

70+
{#snippet newBadge(count: number)}
71+
{#if count > 0}
72+
<Badge class="px-1 py-0">{count} new</Badge>
73+
{/if}
74+
{/snippet}
75+
4076
<div class={cn("flex flex-col", !headless && "*:shadow-lg dark:*:shadow-black", className)}>
4177
<Card.Root
4278
class={{
@@ -67,6 +103,9 @@
67103
<h3 class="text-xl font-bold text-primary">{category.name}</h3>
68104
<ul class="space-y-2">
69105
{#each packages as { pkg } (pkg.name)}
106+
{@const linkedBadgeData =
107+
awaitedOtherReleases.find(r => r.releasesRepo.pkg.name === pkg.name)
108+
?.releases ?? []}
70109
<li>
71110
{#if page.url.pathname.endsWith(`/${pkg.name}`)}
72111
<span class="font-semibold">{pkg.name}</span>
@@ -76,6 +115,7 @@
76115
class="group inline-flex w-full items-center underline-offset-4 hover:underline"
77116
>
78117
{pkg.name}
118+
{@render newBadge(linkedBadgeData.length)}
79119
<ChevronRight
80120
class="ml-auto size-4 text-primary transition-transform group-hover:translate-x-1"
81121
/>
@@ -86,6 +126,9 @@
86126
</ul>
87127
{:else}
88128
{@const firstPackageName = packages[0]?.pkg.name ?? ""}
129+
{@const linkedBadgeData =
130+
awaitedOtherReleases.find(r => r.releasesRepo.pkg.name === firstPackageName)
131+
?.releases ?? []}
89132
{#if page.url.pathname.endsWith(`/${firstPackageName}`)}
90133
<h3 class="text-xl font-bold text-primary underline underline-offset-4">
91134
{category.name}
@@ -96,6 +139,7 @@
96139
class="group inline-flex w-full items-center text-xl font-bold text-primary underline-offset-4 hover:underline"
97140
>
98141
{category.name}
142+
{@render newBadge(linkedBadgeData.length)}
99143
<ChevronRight
100144
class="ml-auto size-4 text-primary transition-transform group-hover:translate-x-1"
101145
/>

0 commit comments

Comments
 (0)