mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-05-04 10:33:47 +01:00
feat(website): display package readme
This commit is contained in:
parent
73618a7958
commit
a31ef0e666
10 changed files with 188 additions and 29 deletions
Binary file not shown.
|
@ -38,9 +38,18 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/nunito-sans": "^5.0.14",
|
||||
"@shikijs/rehype": "^1.13.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"gunzip-maybe": "^1.4.2",
|
||||
"lucide-svelte": "^0.427.0",
|
||||
"tar-stream": "^3.1.7"
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"shiki": "^1.13.0",
|
||||
"tar-stream": "^3.1.7",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,22 @@
|
|||
--color-primary-hover: 255 172 42;
|
||||
--color-primary-bg: 241 157 30;
|
||||
--color-primary-fg: 10 7 4;
|
||||
|
||||
--shiki-foreground: rgb(var(--color-heading));
|
||||
--shiki-background: rgb(var(--color-card));
|
||||
--shiki-token-constant: color-mix(in srgb, rgb(120 140 230), rgb(var(--color-light)) 50%);
|
||||
--shiki-token-string: rgb(var(--color-heading));
|
||||
--shiki-token-comment: rgb(var(--color-body));
|
||||
--shiki-token-keyword: color-mix(in srgb, rgb(var(--color-primary)), rgb(var(--color-light)) 50%);
|
||||
--shiki-token-parameter: rgb(var(--color-heading));
|
||||
--shiki-token-function: rgb(var(--color-primary));
|
||||
--shiki-token-string-expression: color-mix(
|
||||
in srgb,
|
||||
rgb(120 230 140),
|
||||
rgb(var(--color-light)) 50%
|
||||
);
|
||||
--shiki-token-punctuation: rgb(var(--color-heading));
|
||||
--shiki-token-link: rgb(var(--color-primary));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
|
|
@ -28,15 +28,30 @@ export type TargetInfo = {
|
|||
|
||||
export type TargetKind = "roblox" | "lune" | "luau"
|
||||
|
||||
export async function fetchRegistry<T>(
|
||||
export class RegistryHttpError extends Error {
|
||||
name = "RegistryError"
|
||||
constructor(
|
||||
message: string,
|
||||
public response: Response,
|
||||
) {
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRegistryJson<T>(
|
||||
path: string,
|
||||
fetcher: typeof fetch,
|
||||
options?: RequestInit,
|
||||
): Promise<T> {
|
||||
const response = await fetcher(new URL(path, PUBLIC_REGISTRY_URL), options)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch from registry: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const response = await fetchRegistry(path, fetcher, options)
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function fetchRegistry(path: string, fetcher: typeof fetch, options?: RequestInit) {
|
||||
const response = await fetcher(new URL(path, PUBLIC_REGISTRY_URL), options)
|
||||
if (!response.ok) {
|
||||
throw new RegistryHttpError(`Failed to fetch ${response.url}: ${response.statusText}`, response)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { fetchRegistry, type SearchResponse } from "$lib/registry-api"
|
||||
import { fetchRegistryJson, type SearchResponse } from "$lib/registry-api"
|
||||
import type { PageServerLoad } from "./$types"
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch }) => {
|
||||
const { data: packages } = await fetchRegistry<SearchResponse>("search", fetch)
|
||||
const { data: packages } = await fetchRegistryJson<SearchResponse>("search", fetch)
|
||||
|
||||
return { packages }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
fetchRegistry,
|
||||
fetchRegistryJson,
|
||||
RegistryHttpError,
|
||||
type PackageVersionsResponse,
|
||||
type PackageVersionResponse,
|
||||
} from "$lib/registry-api"
|
||||
import { error } from "@sveltejs/kit"
|
||||
import type { LayoutServerLoad } from "./$types"
|
||||
|
||||
type FetchPackageOptions =
|
||||
|
@ -20,23 +22,34 @@ type FetchPackageOptions =
|
|||
const fetchPackage = async (fetcher: typeof fetch, options: FetchPackageOptions) => {
|
||||
const { scope, name } = options
|
||||
|
||||
if ("version" in options) {
|
||||
const { version, target } = options
|
||||
return fetchRegistry<PackageVersionResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version}/${target}`,
|
||||
try {
|
||||
if ("version" in options) {
|
||||
if (options.target === undefined) {
|
||||
error(404, "Not Found")
|
||||
}
|
||||
|
||||
const { version, target } = options
|
||||
return fetchRegistryJson<PackageVersionResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version}/${target}`,
|
||||
fetcher,
|
||||
)
|
||||
}
|
||||
|
||||
const versions = await fetchRegistryJson<PackageVersionsResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}`,
|
||||
fetcher,
|
||||
)
|
||||
|
||||
const latestVersion = versions.at(-1)
|
||||
if (latestVersion === undefined) throw new Error("package has no versions *blows up*")
|
||||
|
||||
return latestVersion
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
error(404, "This package does not exist.")
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
const versions = await fetchRegistry<PackageVersionsResponse>(
|
||||
`packages/${encodeURIComponent(`${scope}/${name}`)}`,
|
||||
fetcher,
|
||||
)
|
||||
|
||||
const latestVersion = versions.at(-1)
|
||||
if (latestVersion === undefined) throw new Error("package has no versions *blows up*")
|
||||
|
||||
return latestVersion
|
||||
}
|
||||
|
||||
export const load: LayoutServerLoad = async ({ params }) => {
|
||||
|
|
|
@ -12,15 +12,21 @@
|
|||
const installCommand = `pesde add ${data.pkg.name}`
|
||||
</script>
|
||||
|
||||
<div class="mx-auto flex max-w-screen-xl px-4 py-16">
|
||||
<div class="mx-auto flex max-w-screen-lg px-4 py-16">
|
||||
<div class="flex-grow pr-4">
|
||||
<h1 class="text-3xl font-bold">
|
||||
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||
</h1>
|
||||
<div class="mb-2 font-semibold text-primary">
|
||||
v{data.pkg.version} · published {formatDistanceToNow(new Date(data.pkg.published_at), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
v{data.pkg.version} ·
|
||||
<time
|
||||
datetime={data.pkg.published_at}
|
||||
title={new Date(data.pkg.published_at).toLocaleString()}
|
||||
>
|
||||
published {formatDistanceToNow(new Date(data.pkg.published_at), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</time>
|
||||
</div>
|
||||
<p class="mb-6 max-w-prose">{data.pkg.description}</p>
|
||||
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { fetchRegistry, RegistryHttpError } from "$lib/registry-api"
|
||||
import { unified } from "unified"
|
||||
import type { PageServerLoad } from "./$types"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkRehype from "remark-rehype"
|
||||
import rehypeSanitize from "rehype-sanitize"
|
||||
import rehypeStringify from "rehype-stringify"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import rehypeShiki from "@shikijs/rehype"
|
||||
import { createCssVariablesTheme } from "shiki"
|
||||
|
||||
const fetchReadme = async (
|
||||
fetcher: typeof fetch,
|
||||
name: string,
|
||||
version: string,
|
||||
target: string,
|
||||
) => {
|
||||
try {
|
||||
const res = await fetchRegistry(
|
||||
`packages/${encodeURIComponent(name)}/${version}/${target}`,
|
||||
fetcher,
|
||||
{
|
||||
headers: {
|
||||
Accept: "text/plain",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return res.text()
|
||||
} catch (e) {
|
||||
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||
return "*No README provided*"
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async ({ parent }) => {
|
||||
const { pkg } = await parent()
|
||||
const { name, version, targets } = pkg
|
||||
|
||||
const readmeText = await fetchReadme(fetch, name, version, targets[0].kind)
|
||||
|
||||
const file = await unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkRehype, { allowDangerousHtml: true })
|
||||
.use(rehypeRaw)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeShiki, {
|
||||
theme: createCssVariablesTheme({
|
||||
name: "css-variables",
|
||||
variablePrefix: "--shiki-",
|
||||
variableDefaults: {},
|
||||
fontStyle: true,
|
||||
}),
|
||||
defaultLanguage: "text",
|
||||
})
|
||||
.use(rehypeStringify)
|
||||
.process(readmeText)
|
||||
|
||||
const readmeHtml = file.value
|
||||
|
||||
return {
|
||||
readmeHtml,
|
||||
pkg,
|
||||
}
|
||||
}
|
|
@ -1 +1,8 @@
|
|||
README
|
||||
<script>
|
||||
const { data } = $props()
|
||||
</script>
|
||||
|
||||
<div class="prose max-w-none py-8">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
{@html data.readmeHtml}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { Config } from "tailwindcss"
|
||||
import defaultTheme from "tailwindcss/defaultTheme"
|
||||
|
||||
const alpha = (color: string, alpha: number = 1) => color.replace("<alpha-value>", alpha.toString())
|
||||
|
||||
export default {
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
|
||||
|
@ -45,6 +47,28 @@ export default {
|
|||
borderColor: {
|
||||
DEFAULT: "rgb(var(--color-border) / <alpha-value>)",
|
||||
},
|
||||
typography: ({ theme }) => ({
|
||||
DEFAULT: {
|
||||
css: {
|
||||
"--tw-prose-body": alpha(theme("colors.body")),
|
||||
"--tw-prose-headings": alpha(theme("colors.heading")),
|
||||
"--tw-prose-lead": alpha(theme("colors.heading")),
|
||||
"--tw-prose-links": alpha(theme("colors.primary").DEFAULT),
|
||||
"--tw-prose-bold": alpha(theme("colors.body")),
|
||||
"--tw-prose-counters": alpha(theme("colors.body")),
|
||||
"--tw-prose-bullets": alpha(theme("colors.border")),
|
||||
"--tw-prose-hr": alpha(theme("colors.border")),
|
||||
"--tw-prose-quotes": alpha(theme("colors.body")),
|
||||
"--tw-prose-quote-borders": alpha(theme("colors.border")),
|
||||
"--tw-prose-captions": alpha(theme("colors.body")),
|
||||
"--tw-prose-code": alpha(theme("colors.body")),
|
||||
"--tw-prose-pre-code": alpha(theme("colors.body")),
|
||||
"--tw-prose-pre-bg": alpha(theme("colors.card").DEFAULT),
|
||||
"--tw-prose-th-borders": alpha(theme("colors.border")),
|
||||
"--tw-prose-td-borders": alpha(theme("colors.border")),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue