feat: website
* feat(website): init * feat(website): home page * feat(website): make page more responsive * feat(website): layout * feat(website): package page * feat(website): update PackageResponse type * feat(website): display package readme * feat(website): use new /latest/any endpoint * feat(website): make website lg instead of xl * fix(website): use NodeJS.Timeout * feat(website): versions page * feat(website): add latest version indicator * feat(website): add target select menu * feat(website): indicate current version * feat(website): add package metadata * feat(website): add hamburger * fix(website): header responsiveness * feat(website): better package layout * feat(website): display authors on package page * fix(website): only display relative dates on client * feat(docs): init docs site * chore(website): read .env from project root * feat(website): add gemoji support * fix(website): overflow on code blocks * chore(docs): read .env from project root * feat(docs): config changes * fix: authors not displaying * fix(website): use fallback language * refactor(website): use predefined target names * refactor(website): change Github to GitHub * chore: remove starter readmes * chore(docs): remove .vscode * chore(docs): remove unused assets folder * fix(website): fix missing datetime attribute * feat(website): switch to universal loaders * feat(docs): search * fix(website): type errors * fix(website): use provided fetch instead of global * feat(website): remove isr * chore(website): add .env.example * feat(website): add icons and metadata * chore(website): add debug logs * chore(website): remove shiki temporarily * fix(website): rehype shiki lazy load * fix(website): use custom highlighter * fix(website): move highlighter creation into load * docs: write docs * feat(website): add og image * feat(website): fix accessibility issues * fix(website): no target selector on mobile * fix(website): close dialog on navigation * fix(website): logo is not a link in hamburger menu * feat(website): dependencies tab * fix(website): use correct dependency target * fix(website): navigation links * feat(website): support wally dependencies * feat(website): metadata + case insensitivity * fix(website): manually implement groupBy `Object.groupBy` isn't supported on Vercel right now. * fix(website): code block with an unknown language * docs(policies): explain & cover more cases * docs: update cli reference * docs: add self hosting registries guide * docs: update README * docs: add more configs to registry guide * fix: favicon and logomark * feat(website): package documentation * fix(website): missing $derive for toc * docs: change SENTRY_URL to SENTRY_DSN * chore(website): remove unused file * chore: remove favicon.zip * fix(website): strip wally# prefix * chore: add changelog entry --------- Co-authored-by: daimond113 <72147841+daimond113@users.noreply.github.com>
2
.env.example
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
PUBLIC_REGISTRY_URL= # url of the registry API, this must have a trailing slash and include the version
|
||||||
|
# example: https://registry.pesde.daimond113.com/v0/
|
|
@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- New website by @lukadev-0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Use updated aliases when reusing lockfile dependencies by @daimond113
|
- Use updated aliases when reusing lockfile dependencies by @daimond113
|
||||||
- Listen for device flow completion without requiring pressing enter by @daimond113
|
- Listen for device flow completion without requiring pressing enter by @daimond113
|
||||||
|
|
34
README.md
|
@ -6,21 +6,23 @@
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
pesde is a package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune.
|
pesde is a package manager for the Luau programming language, supporting
|
||||||
pesde has its own registry, however it can also use Wally, and Git repositories as package sources.
|
multiple runtimes including Roblox and Lune. pesde has its own registry, however
|
||||||
It has been designed with multiple targets in mind, namely Roblox, Lune, and Luau.
|
it can also use Wally, and Git repositories as package sources. It has been
|
||||||
|
designed with multiple targets in mind, namely Roblox, Lune, and Luau.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
pesde can be installed from GitHub Releases. You can find the latest
|
pesde can be installed from GitHub Releases. You can find the latest release
|
||||||
release [here](https://github.com/daimond113/pesde/releases). Once you have downloaded the binary,
|
[here](https://github.com/daimond113/pesde/releases). Once you have downloaded
|
||||||
run the following command to install it:
|
the binary, run the following command to install it:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pesde self-install
|
pesde self-install
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that pesde manages its own versions, so you can update it by running the following command:
|
Note that pesde manages its own versions, so you can update it by running the
|
||||||
|
following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pesde self-upgrade
|
pesde self-upgrade
|
||||||
|
@ -28,19 +30,23 @@ pesde self-upgrade
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For more information about its usage, you can check the [documentation](https://docs.pesde.daimond113.com).
|
For more information about its usage, you can check the
|
||||||
|
[documentation](https://docs.pesde.daimond113.com).
|
||||||
*Currently waiting on [this PR](https://github.com/daimond113/pesde/pull/3) to be merged.*
|
|
||||||
|
|
||||||
## Registry
|
## Registry
|
||||||
|
|
||||||
The main pesde registry is hosted on [fly.io](https://fly.io). You can find it at https://registry.pesde.daimond113.com.
|
The main pesde registry is hosted on [fly.io](https://fly.io). You can find it
|
||||||
|
at https://registry.pesde.daimond113.com.
|
||||||
|
|
||||||
### Self-hosting
|
### Self-hosting
|
||||||
|
|
||||||
The registry tries to require no modifications to be self-hosted. Please refer to the [example .env file](https://github.com/daimond113/pesde/blob/0.5/registry/.env.example) for more information.
|
The registry tries to require no modifications to be self-hosted. Please refer
|
||||||
|
to the
|
||||||
|
[documentation](http://docs.pesde.daimond113.com/guides/self-hosting-registries)
|
||||||
|
for more information.
|
||||||
|
|
||||||
## Previous art
|
## Previous art
|
||||||
|
|
||||||
pesde is heavily inspired by [npm](https://www.npmjs.com/), [pnpm](https://pnpm.io/), [Wally](https://wally.run),
|
pesde is heavily inspired by [npm](https://www.npmjs.com/),
|
||||||
and [Cargo](https://doc.rust-lang.org/cargo/).
|
[pnpm](https://pnpm.io/), [Wally](https://wally.run), and
|
||||||
|
[Cargo](https://doc.rust-lang.org/cargo/).
|
||||||
|
|
3
assets/logomark.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.6025 0L92.9038 25V75L49.6025 100L6.30127 75V25L49.6025 0ZM14.3013 29.6188L49.6025 9.2376L84.9038 29.6188V70.3812L49.6025 90.7624L33.6148 81.5319V67.3848C34.5167 68.5071 35.6388 69.4215 36.981 70.1279C38.9701 71.148 41.0357 71.658 43.1779 71.658C46.442 71.658 49.1452 70.8929 51.2873 69.3629C53.4805 67.7818 55.1126 65.7672 56.1836 63.319C57.0915 61.3382 57.632 59.274 57.8054 57.1263C59.8723 57.7457 62.2157 58.0554 64.8356 58.0554C67.6918 58.0554 70.3695 57.6473 72.8686 56.8313C75.3678 55.9642 77.4079 54.8167 78.989 53.3886L75.7758 47.8038C74.5517 48.9258 72.9961 49.8439 71.109 50.5579C69.2219 51.221 67.2073 51.5525 65.0652 51.5525C61.3929 51.5525 58.6643 50.6854 56.8792 48.9513C56.7195 48.7962 56.567 48.6365 56.4217 48.472C55.6102 47.5539 55.0211 46.4896 54.6546 45.2791L54.6443 45.2452L54.669 45.2791H79.2185V41.9894C79.2185 39.0313 78.5555 36.3536 77.2294 33.9565C75.9543 31.5593 74.0927 29.6467 71.6445 28.2186C69.2474 26.7395 66.3657 26 62.9995 26C59.6843 26 56.8027 26.7395 54.3545 28.2186C51.9064 29.6467 50.0193 31.5593 48.6932 33.9565C47.6743 35.7983 47.0469 37.8057 46.8108 39.9788C45.6888 39.728 44.4778 39.6026 43.1779 39.6026C41.0357 39.6026 38.9701 40.1127 36.981 41.1327C35.3162 41.9651 33.9902 43.1549 33.0028 44.7023V40.3677H20.6855V46.2585H25.8113V77.0266L14.3013 70.3812V29.6188ZM55.1961 36.0986C54.6528 37.1015 54.3321 38.1216 54.234 39.1588H71.7976C71.7976 38.0367 71.4405 36.9401 70.7265 35.8691C70.0634 34.747 69.0689 33.8035 67.7428 33.0384C66.4677 32.2734 64.8867 31.8908 62.9995 31.8908C61.1124 31.8908 59.5058 32.2989 58.1798 33.1149C56.9047 33.88 55.9101 34.8745 55.1961 36.0986ZM49.6451 51.5692C49.3076 50.6641 48.8381 49.871 48.2367 49.1898C48.0885 49.0219 47.9323 48.8609 47.7681 48.7067C46.085 47.0746 44.0449 46.2585 41.6478 46.2585C40.1177 46.2585 38.6131 46.5645 37.134 47.1766C35.8594 47.6773 34.6863 48.5438 33.6148 49.7759V61.47C34.6863 62.6664 35.8594 63.5378 37.134 64.084C38.6131 64.6961 40.1177 65.0021 41.6478 65.0021C44.0449 65.0021 46.085 64.1861 47.7681 62.554C49.4512 60.9219 50.2928 58.6012 50.2928 55.5921C50.2928 54.0679 50.0769 52.727 49.6451 51.5692Z" fill="#F19D1E"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
22
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
.vercel/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
14
docs/.prettierrc
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": false,
|
||||||
|
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.astro",
|
||||||
|
"options": {
|
||||||
|
"parser": "astro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
110
docs/astro.config.mjs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import starlight from "@astrojs/starlight"
|
||||||
|
import tailwind from "@astrojs/tailwind"
|
||||||
|
import { defineConfig } from "astro/config"
|
||||||
|
|
||||||
|
import vercel from "@astrojs/vercel/serverless"
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: "pesde docs",
|
||||||
|
social: {
|
||||||
|
github: "https://github.com/daimond113/pesde",
|
||||||
|
},
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
label: "Intro",
|
||||||
|
items: [{ slug: "" }, { slug: "installation" }, { slug: "quickstart" }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Guides",
|
||||||
|
autogenerate: { directory: "guides" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Reference",
|
||||||
|
autogenerate: { directory: "reference" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Registry",
|
||||||
|
autogenerate: { directory: "registry" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
components: {
|
||||||
|
SiteTitle: "./src/components/SiteTitle.astro",
|
||||||
|
},
|
||||||
|
customCss: ["./src/tailwind.css", "@fontsource-variable/nunito-sans"],
|
||||||
|
favicon: "/favicon.ico",
|
||||||
|
head: [
|
||||||
|
{
|
||||||
|
tag: "meta",
|
||||||
|
attrs: {
|
||||||
|
name: "theme-color",
|
||||||
|
content: "#F19D1E",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "meta",
|
||||||
|
attrs: {
|
||||||
|
property: "og:image",
|
||||||
|
content: "/favicon-48x48.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "meta",
|
||||||
|
attrs: {
|
||||||
|
name: "twitter:card",
|
||||||
|
content: "summary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "link",
|
||||||
|
attrs: {
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/png",
|
||||||
|
href: "/favicon-48x48.png",
|
||||||
|
sizes: "48x48",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "link",
|
||||||
|
attrs: {
|
||||||
|
rel: "icon",
|
||||||
|
type: "image/svg+xml",
|
||||||
|
href: "/favicon.svg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "link",
|
||||||
|
attrs: {
|
||||||
|
rel: "apple-touch-icon",
|
||||||
|
sizes: "180x180",
|
||||||
|
href: "/apple-touch-icon.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "meta",
|
||||||
|
attrs: {
|
||||||
|
name: "apple-mobile-web-app-title",
|
||||||
|
content: "pesde docs",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "link",
|
||||||
|
attrs: {
|
||||||
|
rel: "manifest",
|
||||||
|
href: "/site.webmanifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
tailwind({
|
||||||
|
applyBaseStyles: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
vite: {
|
||||||
|
envDir: "..",
|
||||||
|
},
|
||||||
|
output: "hybrid",
|
||||||
|
adapter: vercel(),
|
||||||
|
})
|
BIN
docs/bun.lockb
Executable file
30
docs/package.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "docs",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"start": "astro dev",
|
||||||
|
"build": "astro check && astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/check": "^0.9.3",
|
||||||
|
"@astrojs/starlight": "^0.28.2",
|
||||||
|
"@astrojs/starlight-tailwind": "^2.0.3",
|
||||||
|
"@astrojs/tailwind": "^5.1.1",
|
||||||
|
"@astrojs/vercel": "^7.8.1",
|
||||||
|
"@fontsource-variable/nunito-sans": "^5.1.0",
|
||||||
|
"@shikijs/rehype": "^1.21.0",
|
||||||
|
"astro": "^4.15.9",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
|
"shiki": "^1.21.0",
|
||||||
|
"tailwindcss": "^3.4.13",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.8"
|
||||||
|
}
|
||||||
|
}
|
BIN
docs/public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/public/favicon-48x48.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
3
docs/public/favicon.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M49.6025 0L92.9038 25V75L49.6025 100L6.30127 75V25L49.6025 0ZM14.3013 29.6188L49.6025 9.2376L84.9038 29.6188V70.3812L49.6025 90.7624L33.6148 81.5319V67.3848C34.5167 68.5071 35.6388 69.4215 36.981 70.1279C38.9701 71.148 41.0357 71.658 43.1779 71.658C46.442 71.658 49.1452 70.8929 51.2873 69.3629C53.4805 67.7818 55.1126 65.7672 56.1836 63.319C57.0915 61.3382 57.632 59.274 57.8054 57.1263C59.8723 57.7457 62.2157 58.0554 64.8356 58.0554C67.6918 58.0554 70.3695 57.6473 72.8686 56.8313C75.3678 55.9642 77.4079 54.8167 78.989 53.3886L75.7758 47.8038C74.5517 48.9258 72.9961 49.8439 71.109 50.5579C69.2219 51.221 67.2073 51.5525 65.0652 51.5525C61.3929 51.5525 58.6643 50.6854 56.8792 48.9513C56.7195 48.7962 56.567 48.6365 56.4217 48.472C55.6102 47.5539 55.0211 46.4896 54.6546 45.2791L54.6443 45.2452L54.669 45.2791H79.2185V41.9894C79.2185 39.0313 78.5555 36.3536 77.2294 33.9565C75.9543 31.5593 74.0927 29.6467 71.6445 28.2186C69.2474 26.7395 66.3657 26 62.9995 26C59.6843 26 56.8027 26.7395 54.3545 28.2186C51.9064 29.6467 50.0193 31.5593 48.6932 33.9565C47.6743 35.7983 47.0469 37.8057 46.8108 39.9788C45.6888 39.728 44.4778 39.6026 43.1779 39.6026C41.0357 39.6026 38.9701 40.1127 36.981 41.1327C35.3162 41.9651 33.9902 43.1549 33.0028 44.7023V40.3677H20.6855V46.2585H25.8113V77.0266L14.3013 70.3812V29.6188ZM55.1961 36.0986C54.6528 37.1015 54.3321 38.1216 54.234 39.1588H71.7976C71.7976 38.0367 71.4405 36.9401 70.7265 35.8691C70.0634 34.747 69.0689 33.8035 67.7428 33.0384C66.4677 32.2734 64.8867 31.8908 62.9995 31.8908C61.1124 31.8908 59.5058 32.2989 58.1798 33.1149C56.9047 33.88 55.9101 34.8745 55.1961 36.0986ZM49.6451 51.5692C49.3076 50.6641 48.8381 49.871 48.2367 49.1898C48.0885 49.0219 47.9323 48.8609 47.7681 48.7067C46.085 47.0746 44.0449 46.2585 41.6478 46.2585C40.1177 46.2585 38.6131 46.5645 37.134 47.1766C35.8594 47.6773 34.6863 48.5438 33.6148 49.7759V61.47C34.6863 62.6664 35.8594 63.5378 37.134 64.084C38.6131 64.6961 40.1177 65.0021 41.6478 65.0021C44.0449 65.0021 46.085 64.1861 47.7681 62.554C49.4512 60.9219 50.2928 58.6012 50.2928 55.5921C50.2928 54.0679 50.0769 52.727 49.6451 51.5692Z" fill="#F19D1E"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
21
docs/public/site.webmanifest
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "pesde",
|
||||||
|
"short_name": "pesde",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#f19d1e",
|
||||||
|
"background_color": "#0a0704",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
BIN
docs/public/web-app-manifest-192x192.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/public/web-app-manifest-512x512.png
Normal file
After Width: | Height: | Size: 15 KiB |
30
docs/src/components/SiteTitle.astro
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a
|
||||||
|
href="https://pesde.daimond113.com/"
|
||||||
|
class="flex text-[var(--sl-color-text-accent)] hover:opacity-80"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 56 28" class="h-7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>pesde</title>
|
||||||
|
<path
|
||||||
|
d="M0 28V26.3156H2.25652V12.2361H0.0635639V10.5517H4.44947L4.48125 11.9819L3.78205 12.3315C4.41769 11.6746 5.16986 11.1661 6.03857 10.8059C6.92846 10.4245 7.82895 10.2338 8.74003 10.2338C9.863 10.2338 10.88 10.4775 11.7911 10.9648C12.7234 11.4522 13.4544 12.1726 13.9841 13.126C14.5349 14.0795 14.8104 15.2448 14.8104 16.6221C14.8104 18.0416 14.5138 19.26 13.9205 20.277C13.3272 21.2728 12.5327 22.0356 11.5368 22.5653C10.5622 23.095 9.5028 23.3598 8.35865 23.3598C7.72301 23.3598 7.11916 23.2751 6.54708 23.1056C5.99619 22.9361 5.50887 22.7242 5.08511 22.4699C4.66135 22.1945 4.34353 21.8873 4.13165 21.5483L4.60838 21.4529L4.5766 26.3156H7.02381V28H0ZM7.94549 21.6118C9.19558 21.6118 10.2444 21.2092 11.0919 20.4041C11.9394 19.5778 12.3632 18.3807 12.3632 16.8127C12.3632 15.2872 11.9606 14.1113 11.1555 13.2849C10.3503 12.4586 9.3333 12.0454 8.1044 12.0454C7.72301 12.0454 7.26747 12.1196 6.73777 12.2679C6.20807 12.395 5.67837 12.6069 5.14867 12.9035C4.61898 13.2002 4.17403 13.5922 3.81383 14.0795L4.5766 12.7446L4.60838 20.7219L3.8774 19.7367C4.42828 20.3299 5.06392 20.7961 5.78431 21.1351C6.5047 21.4529 7.2251 21.6118 7.94549 21.6118Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="M18.37 17.7448C17.0563 17.7448 15.8592 17.4694 14.7786 16.9185C13.7192 16.3676 12.8717 15.5942 12.236 14.5984C11.6216 13.6026 11.3144 12.4478 11.3144 11.1341C11.3144 9.77811 11.611 8.61277 12.2043 7.63813C12.7975 6.64229 13.5921 5.87953 14.5879 5.34983C15.5837 4.79894 16.6961 4.5235 17.925 4.5235C19.1963 4.5235 20.2875 4.78835 21.1986 5.31805C22.1308 5.84775 22.8406 6.59992 23.3279 7.57456C23.8153 8.54921 24.0589 9.69336 24.0589 11.007V11.6109H13.3802L13.412 10.1489H21.7388C21.7388 9.32257 21.5693 8.62337 21.2303 8.05129C20.9125 7.45803 20.4676 7.01308 19.8955 6.71645C19.3234 6.39863 18.6666 6.23972 17.925 6.23972C17.1411 6.23972 16.4313 6.43042 15.7956 6.8118C15.16 7.17199 14.6515 7.70169 14.2701 8.4009C13.9099 9.07891 13.7298 9.90524 13.7298 10.8799C13.7298 11.9181 13.9205 12.8186 14.3019 13.5814C14.6833 14.3229 15.213 14.9056 15.891 15.3294C16.5902 15.732 17.3847 15.9332 18.2746 15.9332C19.2281 15.9332 20.1074 15.7425 20.9125 15.3612C21.7177 14.9798 22.4169 14.503 23.0101 13.931L23.8365 15.3612C23.2644 16.018 22.5228 16.5795 21.6117 17.0456C20.7006 17.5117 19.6201 17.7448 18.37 17.7448Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="M28.7199 22.6288C28.0631 22.6288 27.4275 22.5441 26.813 22.3746C26.2198 22.2051 25.7007 21.972 25.2557 21.6754C24.8108 21.3788 24.4718 21.0292 24.2387 20.6266L24.7154 20.9126V22.311H22.8721V17.9887H24.5247L24.7154 20.0545L24.2705 18.5608C24.5035 19.3447 25.0227 19.9486 25.8278 20.3723C26.6541 20.7961 27.5122 21.008 28.4021 21.008C29.2073 21.008 29.8747 20.8491 30.4044 20.5312C30.9553 20.2134 31.2307 19.7685 31.2307 19.1964C31.2307 18.5184 30.9977 18.0522 30.5315 17.798C30.0866 17.5225 29.3662 17.2789 28.3703 17.067L26.6223 16.6856C25.457 16.389 24.5353 15.997 23.8573 15.5097C23.1793 15.0012 22.8403 14.1642 22.8403 12.9989C22.8403 11.8759 23.2746 11.0178 24.1434 10.4245C25.0332 9.81009 26.135 9.50286 27.4487 9.50286C27.9572 9.50286 28.4869 9.57702 29.0378 9.72534C29.6098 9.85246 30.129 10.0538 30.5951 10.3292C31.0612 10.6046 31.3896 10.9436 31.5803 11.3462L31.1036 11.1873V9.75712H32.9787V14.0477H31.3261L30.9129 11.0284L31.4532 13.126C31.3684 12.7446 31.1248 12.4162 30.7222 12.1408C30.3408 11.8441 29.8853 11.6111 29.3556 11.4416C28.8471 11.2721 28.3386 11.1873 27.8301 11.1873C27.152 11.1873 26.5376 11.325 25.9867 11.6005C25.457 11.8759 25.1922 12.3209 25.1922 12.9353C25.1922 13.4015 25.3723 13.7617 25.7325 14.0159C26.1138 14.2702 26.7283 14.5033 27.5758 14.7151L29.1967 15.0647C30.0018 15.2554 30.7222 15.4885 31.3579 15.7639C32.0147 16.0182 32.5338 16.3996 32.9152 16.9081C33.2966 17.3954 33.4872 18.0946 33.4872 19.0057C33.4872 19.832 33.2542 20.5206 32.788 21.0715C32.3431 21.6012 31.7498 21.9932 31.0083 22.2475C30.2879 22.5017 29.5251 22.6288 28.7199 22.6288Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="M37.1104 18.5607C35.9662 18.5607 34.9068 18.3064 33.9322 17.7979C32.9787 17.2682 32.2054 16.5054 31.6121 15.5096C31.0188 14.5138 30.7222 13.3272 30.7222 11.95C30.7222 10.5304 30.9977 9.34389 31.5485 8.39043C32.1206 7.41579 32.8728 6.67421 33.8051 6.1657C34.7373 5.65719 35.7544 5.40293 36.8561 5.40293C37.746 5.40293 38.6253 5.57243 39.494 5.91144C40.3839 6.22926 41.1679 6.6848 41.8459 7.27807L41.0831 7.5641V1.65266H38.8584V0H43.435V16.5266H45.4055V18.2111H41.0831V16.5584L41.8141 16.6855C41.1997 17.2788 40.5005 17.7449 39.7165 18.0839C38.9537 18.4018 38.085 18.5607 37.1104 18.5607ZM37.6189 16.7809C38.4452 16.7809 39.208 16.622 39.9072 16.3042C40.6276 15.9863 41.2844 15.5626 41.8777 15.0329L41.0831 16.1135V7.85014L41.8777 8.99429C41.2208 8.42221 40.4793 7.97727 39.6529 7.65945C38.8478 7.34163 38.0744 7.18272 37.3329 7.18272C36.5277 7.18272 35.8073 7.37341 35.1717 7.75479C34.5572 8.13618 34.0699 8.69766 33.7097 9.43924C33.3495 10.1596 33.1694 11.0601 33.1694 12.1407C33.1694 13.1366 33.3707 13.9841 33.7733 14.6833C34.1759 15.3613 34.7056 15.8804 35.3624 16.2406C36.0404 16.6008 36.7926 16.7809 37.6189 16.7809Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
<path
|
||||||
|
d="M50.3188 24.2004C49.0051 24.2004 47.808 23.925 46.7274 23.3741C45.668 22.8232 44.8205 22.0498 44.1848 21.054C43.5704 20.0582 43.2632 18.9034 43.2632 17.5898C43.2632 16.2337 43.5598 15.0684 44.1531 14.0937C44.7463 13.0979 45.5409 12.3351 46.5367 11.8054C47.5325 11.2545 48.6449 10.9791 49.8738 10.9791C51.1451 10.9791 52.2363 11.2439 53.1474 11.7736C54.0796 12.3033 54.7894 13.0555 55.2767 14.0302C55.7641 15.0048 56.0077 16.149 56.0077 17.4626V18.0665H45.329L45.3608 16.6045H53.6876C53.6876 15.7782 53.5181 15.079 53.1791 14.5069C52.8613 13.9136 52.4164 13.4687 51.8443 13.172C51.2722 12.8542 50.6154 12.6953 49.8738 12.6953C49.0899 12.6953 48.3801 12.886 47.7444 13.2674C47.1088 13.6276 46.6003 14.1573 46.2189 14.8565C45.8587 15.5345 45.6786 16.3609 45.6786 17.3355C45.6786 18.3737 45.8693 19.2742 46.2507 20.037C46.6321 20.7786 47.1617 21.3612 47.8398 21.785C48.539 22.1876 49.3335 22.3888 50.2234 22.3888C51.1769 22.3888 52.0562 22.1982 52.8613 21.8168C53.6665 21.4354 54.3657 20.9587 54.9589 20.3866L55.7853 21.8168C55.2132 22.4736 54.4716 23.0351 53.5605 23.5012C52.6494 23.9673 51.5688 24.2004 50.3188 24.2004Z"
|
||||||
|
fill="currentColor"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<span class="-mt-px ml-2.5 mr-2 text-xl text-[var(--sl-color-gray-5)]">/</span>
|
||||||
|
<a
|
||||||
|
class="font-medium text-[var(--sl-color-gray-2)] no-underline hover:opacity-80 md:text-lg"
|
||||||
|
href="/">docs</a
|
||||||
|
>
|
||||||
|
</div>
|
6
docs/src/content/config.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { docsSchema } from '@astrojs/starlight/schema';
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
docs: defineCollection({ schema: docsSchema() }),
|
||||||
|
};
|
41
docs/src/content/docs/guides/binary-packages.mdx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
title: Using Binary Packages
|
||||||
|
description: Learn how to use binary packages.
|
||||||
|
---
|
||||||
|
|
||||||
|
A **binary package** is a package that contains a binary export.
|
||||||
|
|
||||||
|
Binary packages can be run like a normal program. There are several ways to use
|
||||||
|
binary packages with pesde.
|
||||||
|
|
||||||
|
## Using `pesde x`
|
||||||
|
|
||||||
|
The `pesde x` command can be used to run a one-off binary package. This is
|
||||||
|
useful for running a binary package without installing it or outside of a pesde
|
||||||
|
project.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde x pesde/hello
|
||||||
|
# Hello, pesde! (pesde/hello@1.0.0, lune)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing a binary package
|
||||||
|
|
||||||
|
Binary packages can be installed using the `pesde add` and `pesde install`
|
||||||
|
commands.
|
||||||
|
|
||||||
|
This requires a `pesde.toml` file to be present in the current directory, and
|
||||||
|
will add the binary package to the `dependencies` section of the file.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add pesde/hello
|
||||||
|
pesde install
|
||||||
|
```
|
||||||
|
|
||||||
|
This will add the binary package to your `PATH`, meaning that it can be run
|
||||||
|
anywhere!
|
||||||
|
|
||||||
|
```sh
|
||||||
|
hello
|
||||||
|
# Hello, pesde! (pesde/hello@1.0.0, lune)
|
||||||
|
```
|
170
docs/src/content/docs/guides/dependencies.mdx
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
---
|
||||||
|
title: Specifying Dependencies
|
||||||
|
description: Learn how to specify dependencies in your pesde project.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside, FileTree, LinkCard } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
The `[dependencies]` section of your `pesde.toml` file is where you specify the
|
||||||
|
dependencies of your project.
|
||||||
|
|
||||||
|
pesde supports multiple types of dependencies.
|
||||||
|
|
||||||
|
## pesde Dependencies
|
||||||
|
|
||||||
|
The most common type of dependency are pesde dependencies. These are
|
||||||
|
dependencies on packages published to a [pesde registry](https://pesde.daimond113.com).
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[indices]
|
||||||
|
default = "https://github.com/daimond113/pesde-index"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hello = { name = "pesde/hello", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, we're specifying a dependency on the `pesde/hello` package on
|
||||||
|
the official pesde registry with a version constraint of `^1.0.0`.
|
||||||
|
|
||||||
|
You can also add a dependency by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add pesde/hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git Dependencies
|
||||||
|
|
||||||
|
Git dependencies are dependencies on packages hosted on a Git repository.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
acme = { repo = "acme/package", rev = "main" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, we're specifying a dependency on the package contained within
|
||||||
|
the `acme/package` GitHub repository at the `main` branch.
|
||||||
|
|
||||||
|
You can also use a URL to specify the Git repository and a specific commit.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
acme = { repo = "https://git.acme.local/package.git", rev = "aeff6" }
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also specify a path if the package is not at the root of the repository.
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- acme/package.git
|
||||||
|
- pkgs/
|
||||||
|
- **foo/**
|
||||||
|
- pesde.toml
|
||||||
|
- ...
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
foo = { repo = "acme/package", rev = "main", path = "pkgs/foo" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The path specified by the Git dependency must either be a valid pesde package or
|
||||||
|
a [Wally][wally] package.
|
||||||
|
|
||||||
|
You can also add a Git dependency by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# From Git URL
|
||||||
|
pesde add https://git.acme.local/package.git#aeff6
|
||||||
|
|
||||||
|
# From GitHub repository
|
||||||
|
pesde add gh#acme/package#main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wally Dependencies
|
||||||
|
|
||||||
|
Wally dependencies are dependencies on packages published to a
|
||||||
|
[Wally registry][wally]. Wally is a package manager for Roblox and thus Wally
|
||||||
|
dependencies should only be used in Roblox projects.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[wally_indices]
|
||||||
|
default = "https://github.com/UpliftGames/wally-index"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
foo = { wally = "acme/package", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, we're specifying a dependency on the `acme/package` package
|
||||||
|
on the official Wally registry with a version constraint of `^1.0.0`.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
|
||||||
|
In order to get proper types support for Wally dependencies, you need to have
|
||||||
|
a [`sourcemap_generator` script](/reference/manifest#sourcemap_generator)
|
||||||
|
specified in your `pesde.toml` file.
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
You can also add a Wally dependency by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add wally#acme/package
|
||||||
|
```
|
||||||
|
|
||||||
|
[wally]: https://wally.run/
|
||||||
|
|
||||||
|
## Workspace Dependencies
|
||||||
|
|
||||||
|
Packages within a workspace can depend on each other. For example, if `foo`
|
||||||
|
and `bar` are both packages in the same workspace, you can add a dependency to
|
||||||
|
`bar` in the `foo/pesde.toml` file:
|
||||||
|
|
||||||
|
```toml title="foo/pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
bar = { workspace = "acme/bar", version = "^" }
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also add a workspace dependency by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add workspace:acme/bar
|
||||||
|
```
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Workspaces"
|
||||||
|
description="Learn more about using workspaces in pesde."
|
||||||
|
href="/guides/workspaces/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Peer Dependencies
|
||||||
|
|
||||||
|
Peer dependencies are dependencies that are not installed automatically when
|
||||||
|
used by another package. They need to be installed by the user of the package.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[peer_dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add a peer dependency by passing `--peer` to the `pesde add` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add --peer acme/foo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dev Dependencies
|
||||||
|
|
||||||
|
Dev dependencies are dependencies that are only used during development. They
|
||||||
|
are not installed when the package is used as a dependency.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dev_dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add a dev dependency by passing `--dev` to the `pesde add` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add --dev acme/foo
|
||||||
|
```
|
80
docs/src/content/docs/guides/overrides.mdx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
title: Overriding Dependencies
|
||||||
|
description: Learn how to override and patch dependencies in pesde.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components'
|
||||||
|
|
||||||
|
pesde has several ways to override or patch dependencies in your project.
|
||||||
|
|
||||||
|
## Dependency Overrides
|
||||||
|
|
||||||
|
Dependency overrides allow you to replace a dependency of a dependency with a
|
||||||
|
different version or package.
|
||||||
|
|
||||||
|
Let's say you have a project with the following dependencies:
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
But `foo` depends on `bar` 1.0.0, and you want to use `bar` 2.0.0 instead. You
|
||||||
|
can override the `bar` dependency in your `pesde.toml` file:
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "^1.0.0" }
|
||||||
|
|
||||||
|
[overrides]
|
||||||
|
"foo>bar" = { name = "acme/bar", version = "^2.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, when you run `pesde install`, `bar` 2.0.0 will be used instead of 1.0.0.
|
||||||
|
|
||||||
|
You can learn more about the syntax for dependency overrides in the
|
||||||
|
[reference](/reference/manifest#overrides).
|
||||||
|
|
||||||
|
## Patching Dependencies
|
||||||
|
|
||||||
|
Patching allows you to modify the source code of a dependency.
|
||||||
|
|
||||||
|
To patch a dependency, you can use the `pesde patch` and `pesde patch-commit`
|
||||||
|
commands.
|
||||||
|
|
||||||
|
Let's say you have the following dependency in your `pesde.toml` file:
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
And you want to patch `foo` to fix a bug. You can run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde patch "acme/foo@1.0.0 luau"
|
||||||
|
|
||||||
|
# done! modify the files in the directory, then run `pesde patch-commit /x/y/z`
|
||||||
|
# to apply.
|
||||||
|
# warning: do not commit these changes
|
||||||
|
# note: the pesde.toml file will be ignored when patching
|
||||||
|
```
|
||||||
|
|
||||||
|
pesde will copy the source code of `foo` to a temporary directory, in this case
|
||||||
|
`/x/y/z`. You can then modify the files in this directory. Once you're done,
|
||||||
|
run `pesde patch-commit /x/y/z` to apply the changes.
|
||||||
|
|
||||||
|
This will create a patch within the `patches` directory of your project, and
|
||||||
|
add an entry to `[patches]`. Then, next time you run `pesde install`, the patch
|
||||||
|
will be applied to the dependency.
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
|
||||||
|
Make sure not to commit or stage the changes made in the temporary directory.
|
||||||
|
Otherwise pesde may not be able to create the patch correctly.
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
94
docs/src/content/docs/guides/publishing.mdx
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
---
|
||||||
|
title: Publishing Packages
|
||||||
|
description: Learn how to publish packages to the pesde registry.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Before you can publish a package, you must configure the required fields in your
|
||||||
|
`pesde.toml` file.
|
||||||
|
|
||||||
|
### `includes`
|
||||||
|
|
||||||
|
The `includes` field is a list of files and directories that should be included
|
||||||
|
in the package.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
includes = [
|
||||||
|
"pesde.toml",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
|
"init.luau",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `target`
|
||||||
|
|
||||||
|
The `target` field defines the environment where the package can be run.
|
||||||
|
|
||||||
|
Here, you must also specify the `lib` and/or `bin` fields to indicate the path
|
||||||
|
of the exported library or binary.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
lib = "init.luau"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Roblox
|
||||||
|
|
||||||
|
`bin` is not supported in Roblox packages. You must also specify a list of
|
||||||
|
`build_files`. These are the files that should be synced into Roblox. They are
|
||||||
|
passed to the `roblox_sync_config_generator` script.
|
||||||
|
|
||||||
|
For more information, see [Roblox](/guides/roblox).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target]
|
||||||
|
environment = "roblox"
|
||||||
|
lib = "init.luau"
|
||||||
|
build_files = ["init.luau"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Before you can publish a package, you must authenticate with your GitHub account.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde auth login
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be given a code and prompted to open the GitHub authentication page in
|
||||||
|
your browser. You must enter the code to authenticate.
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
To publish a package, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde publish
|
||||||
|
```
|
||||||
|
|
||||||
|
You will be prompted to confirm the package details before publishing.
|
||||||
|
|
||||||
|
Once a package is published, others will be able to install it. You may not
|
||||||
|
remove a package once it has been published. You may not publish a package with
|
||||||
|
an already existing version.
|
||||||
|
|
||||||
|
## Multi-target Packages
|
||||||
|
|
||||||
|
You may publish packages under the same name and version but with different
|
||||||
|
targets. This allows you to publish a package that can be used in multiple
|
||||||
|
environments.
|
||||||
|
|
||||||
|
For example, you may publish a package that can be used in both Roblox and
|
||||||
|
Luau environments by publishing two versions of the package, one for each
|
||||||
|
environment.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The `README.md` file in the root of the package will be displayed on the
|
||||||
|
[pesde registry website](https://pesde.daimond113.com/).
|
||||||
|
|
||||||
|
If you have a `docs` directory in the root of the package, they will be
|
||||||
|
hosted by pesde and be accessible on the pesde website.
|
36
docs/src/content/docs/guides/roblox.mdx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: Roblox
|
||||||
|
description: Using pesde in a Roblox project.
|
||||||
|
---
|
||||||
|
|
||||||
|
pesde can be used in Roblox projects, however this requires some extra setup.
|
||||||
|
Namely, you need to specify a `roblox_sync_config_generator` script in order
|
||||||
|
to generate the adequate configuration for the sync tool you are using.
|
||||||
|
|
||||||
|
The [`pesde-scripts`](https://github.com/daimond113/pesde-scripts)
|
||||||
|
repository contains a list of scripts for different sync tools. If the tool
|
||||||
|
you are using is not supported, you can write your own script and submit a PR
|
||||||
|
to get it added.
|
||||||
|
|
||||||
|
These scripts are automatically cloned into the `~/.pesde/scripts` folder and
|
||||||
|
kept up to date when you use pesde.
|
||||||
|
|
||||||
|
## Usage with Rojo
|
||||||
|
|
||||||
|
[Rojo](https://rojo.space/) is a popular tool for syncing files into Roblox
|
||||||
|
Studio.
|
||||||
|
|
||||||
|
Running `pesde init` will prompt you to select a target, select
|
||||||
|
`roblox` or `roblox_server` in this case. This will setup the configuration
|
||||||
|
needed to use pesde in a project using Rojo.
|
||||||
|
|
||||||
|
## Usage with other tools
|
||||||
|
|
||||||
|
If you are using a different sync tool, you should look for it's scripts in the
|
||||||
|
pesde-scripts repository. If you cannot find them, you can write your own and
|
||||||
|
optionally submit a PR to help others using the same tool as you get started
|
||||||
|
quicker.
|
||||||
|
|
||||||
|
Scaffold your project with `pesde init`, select the `roblox` or `roblox_server`
|
||||||
|
target, and then replace the `.pesde/roblox_sync_config_generator.luau` script
|
||||||
|
with the one you want to use.
|
204
docs/src/content/docs/guides/self-hosting-registries.mdx
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
---
|
||||||
|
title: Self Hosting Registries
|
||||||
|
description: Learn how to self host registries for pesde.
|
||||||
|
---
|
||||||
|
|
||||||
|
You can self host registries for pesde. This is useful if you want a private
|
||||||
|
registry or if you a separate registry for other reasons.
|
||||||
|
|
||||||
|
## Making the index repository
|
||||||
|
|
||||||
|
The index is a repository that contains metadata about all the packages in the
|
||||||
|
registry.
|
||||||
|
|
||||||
|
An index contains a `config.toml` file with configuration options.
|
||||||
|
|
||||||
|
To create an index, create a new repository and add a `config.toml` file with
|
||||||
|
the following content:
|
||||||
|
|
||||||
|
```toml title="config.toml"
|
||||||
|
# The URL of the registry API
|
||||||
|
api = "https://registry.acme.local/"
|
||||||
|
|
||||||
|
# Package download URL (optional)
|
||||||
|
download = "{API_URL}/v0/packages/{PACKAGE}/{PACKAGE_VERSION}/{PACKAGE_TARGET}"
|
||||||
|
|
||||||
|
# the client ID of the GitHub OAuth app (optional)
|
||||||
|
github_oauth_client_id = "a1d648966fdfbdcd9295"
|
||||||
|
|
||||||
|
# whether to allow packages with Git dependencies (default: false)
|
||||||
|
git_allowed = true
|
||||||
|
|
||||||
|
# whether to allow packages which depend on packages from other registries
|
||||||
|
# (default: false)
|
||||||
|
other_registries_allowed = true
|
||||||
|
|
||||||
|
# whether to allow packages with Wally dependencies (default: false)
|
||||||
|
wally_allowed = false
|
||||||
|
|
||||||
|
# the maximum size of the archive in bytes (default: 4MB)
|
||||||
|
max_archive_size = 4194304
|
||||||
|
```
|
||||||
|
|
||||||
|
- **api**: The URL of the registry API. See below for more information.
|
||||||
|
|
||||||
|
- **download**: The URL to download packages from. This is optional and
|
||||||
|
defaults to the correct URL for the official pesde registry implementation.
|
||||||
|
You only need this if you are using a custom registry implementation.
|
||||||
|
|
||||||
|
This string can contain the following placeholders:
|
||||||
|
|
||||||
|
- `{API_URL}`: The API URL (as specified in the `api` field).
|
||||||
|
- `{PACKAGE}`: The package name.
|
||||||
|
- `{PACKAGE_VERSION}`: The package version.
|
||||||
|
- `{PACKAGE_TARGET}`: The package target.
|
||||||
|
|
||||||
|
Defaults to `{API_URL}/v0/packages/{PACKAGE}/{PACKAGE_VERSION}/{PACKAGE_TARGET}`.
|
||||||
|
|
||||||
|
- **github_oauth_client_id**: This is required if you use GitHub OAuth for
|
||||||
|
authentication. See below for more information.
|
||||||
|
|
||||||
|
- **git_allowed**: Whether to allow packages with Git dependencies. This is
|
||||||
|
optional and defaults to `false`.
|
||||||
|
|
||||||
|
- **other_registries_allowed**: Whether to allow packages which depend on
|
||||||
|
packages from other registries. This is optional and defaults to `false`.
|
||||||
|
|
||||||
|
- **wally_allowed**: Whether to allow packages with Wally dependencies. This is
|
||||||
|
optional and defaults to `false`.
|
||||||
|
|
||||||
|
- **max_archive_size**: The maximum size of the archive in bytes. This is
|
||||||
|
optional and defaults to `4194304` (4MB).
|
||||||
|
|
||||||
|
You should then push this repository to [GitHub](https://github.com/).
|
||||||
|
|
||||||
|
## Configuring the registry
|
||||||
|
|
||||||
|
The registry is a web server that provides package downloads and the ability to
|
||||||
|
publish packages.
|
||||||
|
|
||||||
|
The official registry implementation is available in the
|
||||||
|
[pesde GitHub repository](https://github.com/daimond113/pesde/tree/0.5/registry).
|
||||||
|
|
||||||
|
Configuring the registry is done using environment variables. In order to allow
|
||||||
|
the registry to access the index repository, you must use a personal access
|
||||||
|
token of a GitHub account that has access to the index repository. We recommend
|
||||||
|
using a separate GitHub account for this purpose.
|
||||||
|
|
||||||
|
For instructions on how to create a personal access token, see the
|
||||||
|
[GitHub documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
|
||||||
|
The access token must have read and write access to the index repository.
|
||||||
|
|
||||||
|
### General configuration
|
||||||
|
|
||||||
|
- **INDEX_REPO_URL**: The URL of the index repository. This is required.\
|
||||||
|
Example: `https://github.com/daimond113/pesde-index.git`
|
||||||
|
|
||||||
|
- **GITHUB_USERNAME**: The username of the GitHub account that has access to the
|
||||||
|
index repository. This is required.
|
||||||
|
|
||||||
|
- **GITHUB_PAT**: The personal access token of the GitHub account specified by
|
||||||
|
`GITHUB_USERNAME`. This is required.
|
||||||
|
|
||||||
|
- **COMMITTER_GIT_NAME**: The name to use for the committer when updating the
|
||||||
|
index repository.\
|
||||||
|
Example: `pesde index updater`
|
||||||
|
|
||||||
|
- **COMMITTER_GIT_EMAIL**: The email to use for the committer when updating the
|
||||||
|
index repository.\
|
||||||
|
Example: `pesde@localhost`
|
||||||
|
|
||||||
|
- **ADDRESS**: The address to bind the server to.\
|
||||||
|
Default: `127.0.0.1`
|
||||||
|
|
||||||
|
- **PORT**: The port to bind the server to.\
|
||||||
|
Default: `8080`
|
||||||
|
|
||||||
|
### Authentication configuration
|
||||||
|
|
||||||
|
The registry supports multiple authentication methods, which are documented
|
||||||
|
below.
|
||||||
|
|
||||||
|
#### General configuration
|
||||||
|
|
||||||
|
- **READ_NEEDS_AUTH**: If set to any value, reading data requires
|
||||||
|
authentication. If not set, anyone can read from the registry.
|
||||||
|
This is optional.
|
||||||
|
|
||||||
|
#### Single token authentication
|
||||||
|
|
||||||
|
Allows read and write access to the registry using a single token.
|
||||||
|
|
||||||
|
- **ACCESS_TOKEN**: The token to use for authentication.
|
||||||
|
|
||||||
|
#### Multiple token authentication
|
||||||
|
|
||||||
|
Allows read and write access to the registry using different tokens.
|
||||||
|
|
||||||
|
- **READ_ACCESS_TOKEN**: The token that grants read access.
|
||||||
|
- **WRITE_ACCESS_TOKEN**: The token that grants write access.
|
||||||
|
|
||||||
|
#### GitHub OAuth authentication
|
||||||
|
|
||||||
|
Allows clients to get read and write access to the registry using GitHub OAuth.
|
||||||
|
This requires a GitHub OAuth app, instructions to create one can be found
|
||||||
|
in the [GitHub documentation](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app).
|
||||||
|
|
||||||
|
- **GITHUB_CLIENT_SECRET**: The client secret of the GitHub OAuth app.
|
||||||
|
|
||||||
|
#### No authentication
|
||||||
|
|
||||||
|
If none of the above variables are set, **anyone** will be able to read and
|
||||||
|
write to the registry.
|
||||||
|
|
||||||
|
### Storage configuration
|
||||||
|
|
||||||
|
The registry supports multiple storage backends, which are documented below.
|
||||||
|
|
||||||
|
#### File system storage
|
||||||
|
|
||||||
|
Stores packages on the file system.
|
||||||
|
|
||||||
|
- **FS_STORAGE_ROOT**: The root directory where packages are stored.
|
||||||
|
|
||||||
|
#### S3 storage
|
||||||
|
|
||||||
|
Stores packages on an S3 compatible storage service, such as
|
||||||
|
[Amazon S3](https://aws.amazon.com/s3/) or
|
||||||
|
[Cloudflare R2](https://www.cloudflare.com/r2/).
|
||||||
|
|
||||||
|
- **S3_ENDPOINT**: The endpoint of the S3 bucket to store packages in.
|
||||||
|
- **S3_BUCKET_NAME**: The name of the bucket.
|
||||||
|
- **S3_REGION**: The region of the bucket.
|
||||||
|
- **S3_ACCESS_KEY**: The access key to use.
|
||||||
|
- **S3_SECRET_KEY**: The secret key to use.
|
||||||
|
|
||||||
|
### Sentry configuration
|
||||||
|
|
||||||
|
The registry supports [Sentry](https://sentry.io/) for error tracking.
|
||||||
|
|
||||||
|
- **SENTRY_DSN**: The DSN of the Sentry instance.
|
||||||
|
|
||||||
|
## Running the registry
|
||||||
|
|
||||||
|
First clone the repository and navigate to the repository directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/daimond113/pesde.git
|
||||||
|
cd pesde
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then build the registry using the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo build --release -p pesde-registry
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build the registry. The resulting binary will be located at
|
||||||
|
`target/release/pesde-registry` or `target/release/pesde-registry.exe`.
|
||||||
|
|
||||||
|
After setting the environment variables, you can run the registry using the
|
||||||
|
by executing the binary.
|
||||||
|
|
||||||
|
The registry must be exposed at the URL specified in the `api` field of the
|
||||||
|
index repository configuration.
|
100
docs/src/content/docs/guides/workspaces.mdx
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
---
|
||||||
|
title: Workspaces
|
||||||
|
description: Learn how to use workspaces in pesde.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { FileTree, LinkCard } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
Workspaces allow you to work with multiple pesde projects within a single
|
||||||
|
repository. Packages within a workspace can depend on each other. And you can
|
||||||
|
run commands like install or publish on every package in the workspace at once.
|
||||||
|
|
||||||
|
Let's say you have a repository with the following structure:
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- pesde.toml
|
||||||
|
- pkgs/
|
||||||
|
- foo/
|
||||||
|
- pesde.toml
|
||||||
|
- ...
|
||||||
|
- bar/
|
||||||
|
- pesde.toml
|
||||||
|
- ...
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
Within the root `pesde.toml` file, we can define a workspace:
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
name = "acme/root"
|
||||||
|
version = "0.0.0"
|
||||||
|
private = "true"
|
||||||
|
|
||||||
|
workspace_members = ["pkgs/*"]
|
||||||
|
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, each folder within the `pkgs/` directory is considered a package in the
|
||||||
|
workspace. You can run commands like `pesde install` or `pesde publish` from
|
||||||
|
the root of the repository to run them on every package in the workspace.
|
||||||
|
|
||||||
|
## Workspace Dependencies
|
||||||
|
|
||||||
|
Packages within a workspace can depend on each other. For example, if `foo`
|
||||||
|
depends on `bar`, you can add a dependency to `bar` in the `foo/pesde.toml` file:
|
||||||
|
|
||||||
|
```toml title="pkgs/foo/pesde.toml"
|
||||||
|
name = "acme/foo"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bar = { workspace = "acme/bar", version = "^" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Workspace dependencies are replaced with normal pesde dependencies when
|
||||||
|
publishing.
|
||||||
|
|
||||||
|
The `version` field can either contain `^`, `*`, `=`, `~`, or a specific version
|
||||||
|
requirement, such as `^1.0.0`. If you use `^`, `=`, or `~`, it will be replaced
|
||||||
|
with the version of the package in the workspace when publishing.
|
||||||
|
|
||||||
|
For example, if you had the following:
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
bar = { workspace = "acme/bar", version = "^" }
|
||||||
|
qux = { workspace = "acme/qux", version = "=" }
|
||||||
|
qar = { workspace = "acme/qar", version = "~" }
|
||||||
|
zoo = { workspace = "acme/zoo", version = "^2.1.0" }
|
||||||
|
baz = { workspace = "acme/baz", version = "*" }
|
||||||
|
```
|
||||||
|
|
||||||
|
If `bar`, `baz`, `qux`, `qar`, and `zoo` are all at version `2.1.5` in the
|
||||||
|
workspace, the `pesde.toml` file will be transformed into the following when
|
||||||
|
publishing.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
bar = { name = "acme/bar", version = "^2.1.5" }
|
||||||
|
qux = { name = "acme/qux", version = "=2.1.5" }
|
||||||
|
qar = { name = "acme/qar", version = "~2.1.5" }
|
||||||
|
zoo = { name = "acme/zoo", version = "^2.1.0" }
|
||||||
|
baz = { name = "acme/baz", version = "*" }
|
||||||
|
```
|
||||||
|
|
||||||
|
A `target` field can be added to the `dependencies` table to specify a target
|
||||||
|
environment for the dependency.
|
||||||
|
|
||||||
|
```toml title="pesde.toml"
|
||||||
|
[dependencies]
|
||||||
|
bar = { workspace = "acme/bar", version = "^", target = "luau" }
|
||||||
|
```
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Specifying Dependencies"
|
||||||
|
description="Learn more about specifying dependencies in pesde."
|
||||||
|
href="/guides/dependencies/"
|
||||||
|
/>
|
32
docs/src/content/docs/index.mdx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
---
|
||||||
|
title: What is pesde?
|
||||||
|
description: A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune.
|
||||||
|
---
|
||||||
|
|
||||||
|
pesde is a package manager for the Luau programming language.
|
||||||
|
|
||||||
|
## Why use pesde?
|
||||||
|
|
||||||
|
When you write code, you often want to use libraries or frameworks that others
|
||||||
|
have written. Manually downloading and managing these can be cumbersome.
|
||||||
|
|
||||||
|
These libraries or frameworks can be distributed as packages. You can then
|
||||||
|
easily install and use these packages using pesde. pesde will automatically
|
||||||
|
download and manage the packages, and their dependencies, for you.
|
||||||
|
|
||||||
|
## Multi-target support
|
||||||
|
|
||||||
|
Luau can run in a lot of different places, such as on [Roblox][roblox], or in
|
||||||
|
[Lune][lune].
|
||||||
|
|
||||||
|
pesde is designed to work with all of these runtimes. Packages can publish
|
||||||
|
multiple versions of themselves, each tailored to a specific runtime.
|
||||||
|
|
||||||
|
[registry]: https://pesde.daimond113.com/
|
||||||
|
[roblox]: https://www.roblox.com/
|
||||||
|
[lune]: https://lune-org.github.io/docs
|
||||||
|
|
||||||
|
## The pesde registry
|
||||||
|
|
||||||
|
The [pesde registry][registry] is where anyone can publish their packages for
|
||||||
|
others to use.
|
89
docs/src/content/docs/installation.mdx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
description: Install pesde
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside, Steps, TabItem, Tabs } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
pesde requires [Lune](https://lune-org.github.io/docs) to be installed on your
|
||||||
|
system in order to function properly.
|
||||||
|
|
||||||
|
You can follow the installation instructions in the
|
||||||
|
[Lune documentation](https://lune-org.github.io/docs/getting-started/1-installation).
|
||||||
|
|
||||||
|
## Installing pesde
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. Go to the [GitHub releases page](https://github.com/daimond113/pesde/releases/latest).
|
||||||
|
|
||||||
|
2. Download the corresponding archive for your operating system. You can choose
|
||||||
|
whether to use the `.zip` or `.tar.gz` files.
|
||||||
|
|
||||||
|
3. Extract the downloaded archive to a folder on your computer.
|
||||||
|
|
||||||
|
4. Open a terminal and locate the path of the extracted `pesde` binary.
|
||||||
|
|
||||||
|
<Tabs syncKey="os">
|
||||||
|
<TabItem label="Windows">
|
||||||
|
|
||||||
|
If you extracted the archive to `C:\Users\User\Downloads`, the path to the
|
||||||
|
`pesde` binary would be `C:\Users\User\Downloads\pesde.exe`.
|
||||||
|
|
||||||
|
You can then run the `self-install` command:
|
||||||
|
|
||||||
|
```ps
|
||||||
|
C:\Users\User\Downloads\pesde.exe self-install
|
||||||
|
```
|
||||||
|
|
||||||
|
pesde should now be installed on your system. You may need to restart your
|
||||||
|
computer for the changes to take effect.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Linux & macOS">
|
||||||
|
|
||||||
|
If you extracted the archive to `~/Downloads`, the path to the `pesde`
|
||||||
|
binary would be `~/Downloads/pesde`.
|
||||||
|
|
||||||
|
You must then add execute permissions and run the `self-install` command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod +x ~/Downloads/pesde
|
||||||
|
~/Downloads/pesde self-install
|
||||||
|
```
|
||||||
|
|
||||||
|
pesde should now be installed on your system. You will need to update your
|
||||||
|
shell configuration file to add the pesde binary to your `PATH`
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
```sh title=".zshrc"
|
||||||
|
export PATH = "$PATH:/home/user/.pesde/bin"
|
||||||
|
```
|
||||||
|
|
||||||
|
You should then be able to run `pesde` after restarting your shell.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
5. Verify that pesde is installed by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde -v
|
||||||
|
```
|
||||||
|
|
||||||
|
This command should output the version of pesde that you installed.
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
|
||||||
|
It is not recommended to use toolchain managers (such as Rokit or Aftman) to
|
||||||
|
install pesde. You can use `pesde self-upgrade` if you need to update pesde.
|
||||||
|
|
||||||
|
If you need everyone to use the same version of pesde, you can use the
|
||||||
|
`pesde_version` field in `pesde.toml` to specify the version of pesde to use
|
||||||
|
for the current project.
|
||||||
|
|
||||||
|
</Aside>
|
142
docs/src/content/docs/quickstart.mdx
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
---
|
||||||
|
title: Quickstart
|
||||||
|
description: Start using pesde
|
||||||
|
---
|
||||||
|
|
||||||
|
import { FileTree } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
Let's make a simple Luau program that uses the `pesde/hello` package to print
|
||||||
|
hello to the terminal.
|
||||||
|
|
||||||
|
## Scaffolding the project
|
||||||
|
|
||||||
|
In your terminal, run the following commands to create a folder and navigate
|
||||||
|
into it.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir hello-pesde
|
||||||
|
cd hello-pesde
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, we'll use `pesde init` to scaffold a new pesde project. The command will
|
||||||
|
ask you a few questions to set up the project. Our project will be named
|
||||||
|
`<username>/hello_pesde`, replace `<username>` with a username of your choice.
|
||||||
|
The name may only contain lowercase letters, numbers, and underscores. The
|
||||||
|
environment we're targeting is `luau`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde init
|
||||||
|
|
||||||
|
# What is the name of the project? <username>/hello_pesde
|
||||||
|
# What is the description of the project? (leave empty for none)
|
||||||
|
# Who are the authors of this project? (leave empty for none, comma separated)
|
||||||
|
# What is the repository URL of this project? (leave empty for none)
|
||||||
|
# What is the license of this project? (leave empty for none) MIT
|
||||||
|
# What environment are you targeting for your package? luau
|
||||||
|
# Would you like to setup a default roblox_sync_config_generator script? No
|
||||||
|
```
|
||||||
|
|
||||||
|
The command will create a `pesde.toml` file in the current folder. Go ahead
|
||||||
|
and open this file in your text editor of choice.
|
||||||
|
|
||||||
|
## Adding a main script
|
||||||
|
|
||||||
|
Under the `[target]` section, we're going to add a `bin` field to specify
|
||||||
|
the path to the main script of our package.
|
||||||
|
|
||||||
|
```diff lang="toml" title="pesde.toml"
|
||||||
|
name = "<username>/hello_pesde"
|
||||||
|
version = "0.1.0"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
+ bin = "main.luau"
|
||||||
|
|
||||||
|
[indices]
|
||||||
|
default = "https://github.com/daimond113/pesde-index"
|
||||||
|
```
|
||||||
|
|
||||||
|
Don't forget to save the file after making the changes.
|
||||||
|
|
||||||
|
Now, lets create a `main.luau` file in the project folder and add the following
|
||||||
|
code to it.
|
||||||
|
|
||||||
|
```luau title="main.luau"
|
||||||
|
print("Hello, pesde!")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the script
|
||||||
|
|
||||||
|
Then, we can run the following command to run the script.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde run
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see `Hello, pesde!` printed to the terminal.
|
||||||
|
|
||||||
|
## Install a dependency
|
||||||
|
|
||||||
|
Let's use the `pesde/hello` package instead of printing ourselves.
|
||||||
|
|
||||||
|
Run the following command to add the package to `pesde.toml`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add pesde/hello
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see that `pesde.toml` has been updated with the new dependency.
|
||||||
|
|
||||||
|
```diff lang="toml" title="pesde.toml"
|
||||||
|
name = "lukadev_0/hello_pesde"
|
||||||
|
version = "0.1.0"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
bin = "main.luau"
|
||||||
|
|
||||||
|
[indices]
|
||||||
|
default = "https://github.com/daimond113/pesde-index"
|
||||||
|
|
||||||
|
+ [dependencies]
|
||||||
|
+ hello = { name = "pesde/hello", version = "^1.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the following command to install the new dependency.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde install
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see that pesde has created a `luau_packages` folder containing the
|
||||||
|
newly installed package. It has alsoo created a `pesde.lock` file, this file
|
||||||
|
contains the exact versions of the dependencies that were installed so that
|
||||||
|
they can be installed again in the future.
|
||||||
|
|
||||||
|
<FileTree>
|
||||||
|
|
||||||
|
- luau_packages/
|
||||||
|
- hello.luau
|
||||||
|
- ...
|
||||||
|
- main.luau
|
||||||
|
- pesde.lock
|
||||||
|
- pesde.toml
|
||||||
|
|
||||||
|
</FileTree>
|
||||||
|
|
||||||
|
Let's update the `main.luau` file to use the `pesde/hello` package.
|
||||||
|
|
||||||
|
```luau title="main.luau"
|
||||||
|
local hello = require("./luau_packages/hello")
|
||||||
|
|
||||||
|
hello()
|
||||||
|
```
|
||||||
|
|
||||||
|
If we run the script again, we should see something printed to the terminal.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde run
|
||||||
|
# Hello, pesde! (pesde/hello@1.0.0, luau)
|
||||||
|
```
|
188
docs/src/content/docs/reference/cli.mdx
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
---
|
||||||
|
title: pesde CLI
|
||||||
|
description: Reference for the pesde CLI.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { LinkCard } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
The pesde CLI is the primary way to interact with pesde projects. It provides
|
||||||
|
commands for installing dependencies, running scripts, and more.
|
||||||
|
|
||||||
|
## `pesde auth`
|
||||||
|
|
||||||
|
Authentication-related commands.
|
||||||
|
|
||||||
|
- `-i, --index`: The index of which token to manipulate. May be a URL or an alias.
|
||||||
|
Defaults to the default
|
||||||
|
index of the current project or the default index set in the config.
|
||||||
|
|
||||||
|
### `pesde auth login`
|
||||||
|
|
||||||
|
Sets the token for the index.
|
||||||
|
|
||||||
|
- `-t, --token`: The token to set.
|
||||||
|
|
||||||
|
If no token is provided, you will be prompted to authenticate with GitHub. A
|
||||||
|
code will be provided that you can paste into the GitHub authentication prompt.
|
||||||
|
|
||||||
|
### `pesde auth logout`
|
||||||
|
|
||||||
|
Removes the stored token for the index.
|
||||||
|
|
||||||
|
### `pesde auth whoami`
|
||||||
|
|
||||||
|
Prints the username of the currently authenticated user of the index. Only
|
||||||
|
works if the token is a GitHub token.
|
||||||
|
|
||||||
|
## `pesde config`
|
||||||
|
|
||||||
|
Configuration-related commands.
|
||||||
|
|
||||||
|
### `pesde config default-index`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde config default-index [INDEX]
|
||||||
|
```
|
||||||
|
|
||||||
|
Configures the default index. If no index is provided, the current default index
|
||||||
|
is printed.
|
||||||
|
|
||||||
|
- `-r, --reset`: Resets the default index.
|
||||||
|
|
||||||
|
The default index is [`pesde-index`](https://github.com/daimond113/pesde-index).
|
||||||
|
|
||||||
|
### `pesde config scripts-repo`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde config scripts-repo [REPO]
|
||||||
|
```
|
||||||
|
|
||||||
|
Configures the scripts repository. If no repository is provided, the current
|
||||||
|
scripts repository is printed.
|
||||||
|
|
||||||
|
- `-r, --reset`: Resets the scripts repository.
|
||||||
|
|
||||||
|
The default scripts repository is [`pesde-scripts`](https://github.com/daimond113/pesde-scripts).
|
||||||
|
|
||||||
|
## `pesde init`
|
||||||
|
|
||||||
|
Initializes a new pesde project in the current directory.
|
||||||
|
|
||||||
|
## `pesde run`
|
||||||
|
|
||||||
|
Runs a script from the current project using Lune.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde run [SCRIPT] [ -- <ARGS>...]
|
||||||
|
```
|
||||||
|
|
||||||
|
If no script is provided, it will run the script specified by `target.bin`
|
||||||
|
in `pesde.toml`.
|
||||||
|
|
||||||
|
If a path is provided, it will run the script at that path.
|
||||||
|
|
||||||
|
If a script defined in `[scripts]` is provided, it will run that script.
|
||||||
|
|
||||||
|
If a package name is provided, it will run the script specified by `target.bin`
|
||||||
|
in that package.
|
||||||
|
|
||||||
|
Arguments can be passed to the script by using `--` followed by the arguments.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde run foo -- --arg1 --arg2
|
||||||
|
```
|
||||||
|
|
||||||
|
## `pesde install`
|
||||||
|
|
||||||
|
Installs dependencies for the current project.
|
||||||
|
|
||||||
|
- `-t, --threads`: The number of threads to use for downloading dependencies.
|
||||||
|
- `--locked`: Whether to error if the lockfile is out of date.
|
||||||
|
- `--prod`: Whether to skip installing dev dependencies.
|
||||||
|
|
||||||
|
## `pesde publish`
|
||||||
|
|
||||||
|
Publishes the current project to the pesde registry.
|
||||||
|
|
||||||
|
- `-d, --dry-run`: Whether to perform a dry run. This will output a
|
||||||
|
tarball containing the package that would be published, but will not actually
|
||||||
|
publish it.
|
||||||
|
- `-y, --yes`: Whether to skip the confirmation prompt.
|
||||||
|
|
||||||
|
## `pesde self-install`
|
||||||
|
|
||||||
|
Performs the pesde installation process. This should be the first command run
|
||||||
|
after downloading the pesde binary.
|
||||||
|
|
||||||
|
## `pesde self-upgrade`
|
||||||
|
|
||||||
|
Upgrades the pesde binary to the latest version.
|
||||||
|
|
||||||
|
## `pesde patch`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde patch <PACKAGE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Prepares a patching environment for a package. This will copy the source code of
|
||||||
|
the package to a temporary directory.
|
||||||
|
|
||||||
|
The package specified must be in the format `<name>@<version> <target>`.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Overrides"
|
||||||
|
description="Learn more about overriding and patching packages."
|
||||||
|
href="/guides/overrides/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## `pesde patch-commit`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde patch-commit <PATH>
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies the changes made in the patching environment created by `pesde patch`.
|
||||||
|
|
||||||
|
## `pesde add`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add <PACKAGE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Adds a package to the dependencies of the current project.
|
||||||
|
|
||||||
|
- `-i, --index <INDEX>`: The index in which to search for the package.
|
||||||
|
- `-t, --target <TARGET>`: The target environment for the package.
|
||||||
|
- `-a, --alias <ALIAS>`: The alias to use for the package, defaults to the
|
||||||
|
package name.
|
||||||
|
- `-p, --peer`: Adds the package as a peer dependency.
|
||||||
|
- `-d, --dev`: Adds the package as a dev dependency.
|
||||||
|
|
||||||
|
The following formats are supported:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde add pesde/hello
|
||||||
|
pesde add gh#acme/package#main
|
||||||
|
pesde add https://git.acme.local/package.git#aeff6
|
||||||
|
```
|
||||||
|
|
||||||
|
## `pesde update`
|
||||||
|
|
||||||
|
Updates the dependencies of the current project.
|
||||||
|
|
||||||
|
- `-t, --threads`: The number of threads to use for downloading dependencies.
|
||||||
|
|
||||||
|
## `pesde x`
|
||||||
|
|
||||||
|
Runs a one-off binary package.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde x <PACKAGE>
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for running a binary package without installing it or outside of
|
||||||
|
a pesde project.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pesde x pesde/hello
|
||||||
|
```
|
405
docs/src/content/docs/reference/manifest.mdx
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
---
|
||||||
|
title: pesde.toml
|
||||||
|
description: Reference for `pesde.toml`
|
||||||
|
---
|
||||||
|
|
||||||
|
import { LinkCard } from "@astrojs/starlight/components"
|
||||||
|
|
||||||
|
`pesde.toml` is the manifest file for a pesde package. It contains metadata about
|
||||||
|
the package and its dependencies.
|
||||||
|
|
||||||
|
## Top-level fields
|
||||||
|
|
||||||
|
```toml
|
||||||
|
name = "acme/package"
|
||||||
|
version = "1.2.3"
|
||||||
|
description = "A package that does foo and bar"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["John Doe <john.doe@acme.local> (https://acme.local)"]
|
||||||
|
repository = "https://github.com/acme/package"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `name`
|
||||||
|
|
||||||
|
The name of the package. This is used to identify the package in the registry.
|
||||||
|
|
||||||
|
The name consists of a scope and a package name, separated by a slash (`/`). It
|
||||||
|
may only contain lowercase letters, numbers, and underscores.
|
||||||
|
|
||||||
|
The first one to publish to a given scope gets to own it. If you want multiple
|
||||||
|
people to be able to publish to the same scope, you can send a pull request to
|
||||||
|
the [pesde-index GitHub repository](https://github.com/daimond113/pesde-index)
|
||||||
|
and add the GitHub user ID of the other person to the `owners` field of the
|
||||||
|
`scope.toml` file of the given scope. For more information, see
|
||||||
|
[policies](/registry/policies#package-ownership).
|
||||||
|
|
||||||
|
### `version`
|
||||||
|
|
||||||
|
The version of the package. This must be a valid [SemVer](https://semver.org/)
|
||||||
|
version, such as `1.2.3`.
|
||||||
|
|
||||||
|
### `description`
|
||||||
|
|
||||||
|
A short description of the package. This is displayed on the package page in the
|
||||||
|
registry.
|
||||||
|
|
||||||
|
### `license`
|
||||||
|
|
||||||
|
The license of the package. It is recommended to use a
|
||||||
|
[SPDX license identifier](https://spdx.org/licenses/), such as `MIT` or
|
||||||
|
`Apache-2.0`.
|
||||||
|
|
||||||
|
### `authors`
|
||||||
|
|
||||||
|
A list of authors of the package. Each author is a string containing the name of
|
||||||
|
the author, optionally followed by an email address in angle brackets, and a
|
||||||
|
website URL in parentheses. For example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
authors = ["John Doe <john.doe@acme.local> (https://acme.local)"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `repository`
|
||||||
|
|
||||||
|
The URL of the repository where the package is hosted. This is displayed on the
|
||||||
|
package page in the registry.
|
||||||
|
|
||||||
|
### `private`
|
||||||
|
|
||||||
|
A boolean indicating whether the package is private. If set to `true`, the
|
||||||
|
package cannot be published to the registry.
|
||||||
|
|
||||||
|
### `includes`
|
||||||
|
|
||||||
|
List of top-level files and directories to include in the package when
|
||||||
|
publishing. Files not listed here will not be published.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
includes = [
|
||||||
|
"pesde.toml",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE",
|
||||||
|
"init.luau",
|
||||||
|
"docs",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pesde_version`
|
||||||
|
|
||||||
|
The version of pesde to use within this project. The `pesde` CLI will look at
|
||||||
|
this field and run the correct version of pesde for this project.
|
||||||
|
|
||||||
|
### `workspace_members`
|
||||||
|
|
||||||
|
A list of globs containing the members of this workspace.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Workspaces"
|
||||||
|
description="Learn more about workspaces in pesde."
|
||||||
|
href="/guides/workspaces/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
## `[target]`
|
||||||
|
|
||||||
|
The `[target]` section contains information about the target platform for the
|
||||||
|
package.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target]
|
||||||
|
environment = "luau"
|
||||||
|
lib = "init.luau"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `environment`
|
||||||
|
|
||||||
|
The target environment for the package. This can be one of the following:
|
||||||
|
|
||||||
|
- `luau`: Standalone Luau code that can be run using the `luau` CLI.
|
||||||
|
- `lune`: Luau code that requires the Lune runtime.
|
||||||
|
- `roblox`: Luau code that must be run in Roblox.
|
||||||
|
- `roblox_server`: Same as `roblox`, but only for server-side code.
|
||||||
|
|
||||||
|
### `lib`
|
||||||
|
|
||||||
|
**Allowed in:** `luau`, `lune`, `roblox`, `roblox_server`
|
||||||
|
|
||||||
|
The entry point of the library exported by the package. This file is what will
|
||||||
|
be required when the package is loaded using `require`.
|
||||||
|
|
||||||
|
### `bin`
|
||||||
|
|
||||||
|
**Allowed in:** `luau`, `lune`
|
||||||
|
|
||||||
|
The entry point of the binary exported by the package. This file is what will be
|
||||||
|
run when the package is executed as a binary.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Using Binary Packages"
|
||||||
|
description="Learn more about using binary packages in pesde."
|
||||||
|
href="/guides/binary-packages/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
### `build_files`
|
||||||
|
|
||||||
|
**Allowed in:** `roblox`, `roblox_server`
|
||||||
|
|
||||||
|
A list of files that should be synced to Roblox when the package is installed.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
build_files = [
|
||||||
|
"init.luau",
|
||||||
|
"foo.luau",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
These files are passed to [`roblox_sync_config_generator`](#roblox_sync_config_generator)
|
||||||
|
when the package is installed in order to generate the necessary configuration.
|
||||||
|
|
||||||
|
## `[scripts]`
|
||||||
|
|
||||||
|
The `[scripts]` section contains scripts that can be run using the `pesde run`
|
||||||
|
command. These scripts are run using [Lune](https://lune-org.github.io/docs).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[scripts]
|
||||||
|
build = "sripts/build.luau"
|
||||||
|
test = "scripts/test.luau"
|
||||||
|
```
|
||||||
|
|
||||||
|
There are also a few special scripts that are run in certain cases by pesde.
|
||||||
|
|
||||||
|
### `roblox_sync_config_generator`
|
||||||
|
|
||||||
|
This is responsible for generating adequate configuration files for Roblox
|
||||||
|
sync tools.
|
||||||
|
|
||||||
|
`process.args` will contain the directory containing the package, and the list
|
||||||
|
of files specified within the [`target.build_files`](#build_files) of the
|
||||||
|
package.
|
||||||
|
|
||||||
|
You can find template scripts inside the
|
||||||
|
[`pesde-scripts` repository](https://github.com/daimond113/pesde-scripts)
|
||||||
|
for various sync tools.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Roblox"
|
||||||
|
description="Learn more about using pesde in Roblox projects."
|
||||||
|
href="/guides/roblox/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Example script for Rojo"
|
||||||
|
description="An example script for generating configuration for Rojo."
|
||||||
|
href="https://github.com/daimond113/pesde-scripts/blob/master/lune/rojo/roblox_sync_config_generator.luau"
|
||||||
|
/>
|
||||||
|
|
||||||
|
### `sourcemap_generator`
|
||||||
|
|
||||||
|
This is responsible for generating source maps for packages that are installed.
|
||||||
|
This is required to get proper types support when using
|
||||||
|
[Wally dependencies](/guides/dependencies/#wally-dependencies).
|
||||||
|
|
||||||
|
The script will receive the path to the package directory as the first argument
|
||||||
|
through `process.args`.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Example script for Rojo"
|
||||||
|
description="An example script for generating configuration for Rojo."
|
||||||
|
href="https://github.com/daimond113/pesde-scripts/blob/master/lune/rojo/sourcemap_generator.luau"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## `[indices]`
|
||||||
|
|
||||||
|
The `[indices]` section contains a list of pesde indices where packages can be
|
||||||
|
installed from.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[indices]
|
||||||
|
default = "https://github.com/daimond113/pesde-index"
|
||||||
|
acme = "https://github.com/acme/pesde-index"
|
||||||
|
```
|
||||||
|
|
||||||
|
These can then be referenced in the [`dependencies`](#dependencies) of the
|
||||||
|
package. The `default` index is used if no index is specified.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "1.2.3", index = "acme" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## `[wally_indices]`
|
||||||
|
|
||||||
|
The `[wally_indices]` section contains a list of Wally indices where packages
|
||||||
|
can be installed from. This is used for
|
||||||
|
[Wally dependencies](/guides/dependencies/#wally-dependencies).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[wally_indices]
|
||||||
|
default = "https://github.com/UpliftGames/wally-index"
|
||||||
|
acme = "https://github.com/acme/wally-index"
|
||||||
|
```
|
||||||
|
|
||||||
|
These can then be referenced in the [`dependencies`](#dependencies) of the
|
||||||
|
package. The `default` index is used if no index is specified.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { wally = "acme/foo", version = "1.2.3", index = "acme" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## `[overrides]`
|
||||||
|
|
||||||
|
The `[overrides]` section contains a list of overrides for dependencies. This
|
||||||
|
allows you to replace certain dependencies with different versions or even
|
||||||
|
different packages.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[overrides]
|
||||||
|
"bar>baz" = { name = "acme/baz", version = "1.0.0" }
|
||||||
|
"foo>bar,baz>bar" = { name = "acme/bar", version = "2.0.0" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The above example will replace the `baz` dependency of the `bar` package with
|
||||||
|
version `1.0.0`, and the `bar` and `baz` dependencies of the `foo` package with
|
||||||
|
version `2.0.0`.
|
||||||
|
|
||||||
|
Each key in the overrides table is a comma-separated list of package paths. The
|
||||||
|
path is a list of package names separated by `>`. For example, `foo>bar>baz`
|
||||||
|
refers to the `baz` dependency of the `bar` package, which is a dependency of
|
||||||
|
the `foo` package.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Overrides"
|
||||||
|
description="Learn more about overriding and patching packages."
|
||||||
|
href="/guides/overrides/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## `[patches]`
|
||||||
|
|
||||||
|
The `[patches]` section contains a list of patches for dependencies. This allows
|
||||||
|
you to modify the source code of dependencies.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[patches]
|
||||||
|
"acme/foo" = { "1.0.0 luau" = "patches/acme+foo-1.0.0+luau.patch" }
|
||||||
|
```
|
||||||
|
|
||||||
|
The above example will patch version `1.0.0` with the `luau` target of the
|
||||||
|
`acme/foo` package using the `patches/acme+foo-1.0.0+luau.patch` file.
|
||||||
|
|
||||||
|
Each key in the patches table is the package name, and the value is a table
|
||||||
|
where the keys are the version and target, and the value is the path to the
|
||||||
|
patch.
|
||||||
|
|
||||||
|
The patches can be generated using the `pesde patch` command.
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Overrides"
|
||||||
|
description="Learn more about overriding and patching packages."
|
||||||
|
href="/guides/overrides/"
|
||||||
|
/>
|
||||||
|
|
||||||
|
## `[place]`
|
||||||
|
|
||||||
|
This is used in Roblox projects to specify where packages are located in the
|
||||||
|
Roblox datamodel.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[place]
|
||||||
|
shared = "game.ReplicatedStorage.Packages"
|
||||||
|
server = "game.ServerScriptService.Packages"
|
||||||
|
```
|
||||||
|
|
||||||
|
## `[dependencies]`
|
||||||
|
|
||||||
|
The `[dependencies]` section contains a list of dependencies for the package.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "1.2.3" }
|
||||||
|
bar = { wally = "acme/bar", version = "2.3.4" }
|
||||||
|
baz = { git = "acme/baz", rev = "main" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Each key in the dependencies table is the name of the dependency, and the value
|
||||||
|
is a dependency specifier.
|
||||||
|
|
||||||
|
There are several types of dependency specifiers.
|
||||||
|
|
||||||
|
### pesde
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "1.2.3", index = "acme", target = "lune" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**pesde dependencies** contain the following fields:
|
||||||
|
|
||||||
|
- `name`: The name of the package.
|
||||||
|
- `version`: The version of the package.
|
||||||
|
- `index`: The [pesde index](#indices) to install the package from. If not
|
||||||
|
specified, the `default` index is used.
|
||||||
|
- `target`: The target platform for the package. If not specified, the target
|
||||||
|
platform of the current package is used.
|
||||||
|
|
||||||
|
### Wally
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { wally = "acme/foo", version = "1.2.3", index = "acme" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Wally dependencies** contain the following fields:
|
||||||
|
|
||||||
|
- `wally`: The name of the package.
|
||||||
|
- `version`: The version of the package.
|
||||||
|
- `index`: The [Wally index](#wally_indices) to install the package from. If not
|
||||||
|
specified, the `default` index is used.
|
||||||
|
|
||||||
|
### Git
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
foo = { git = "acme/packages", rev = "main", path = "foo" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Git dependencies** contain the following fields:
|
||||||
|
|
||||||
|
- `git`: The URL of the Git repository.
|
||||||
|
This can either be `<owner>/<name>` for a GitHub repository, or a full URL.
|
||||||
|
- `rev`: The Git revision to install. This can be a branch, tag, or commit hash.
|
||||||
|
- `path`: The path within the repository to install. If not specified, the root
|
||||||
|
of the repository is used.
|
||||||
|
|
||||||
|
## `[peer_dependencies]`
|
||||||
|
|
||||||
|
The `[peer_dependencies]` section contains a list of peer dependencies for the
|
||||||
|
package. These are dependencies that are required by the package, but are not
|
||||||
|
installed automatically. Instead, they must be installed by the user of the
|
||||||
|
package.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[peer_dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "1.2.3" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## `[dev_dependencies]`
|
||||||
|
|
||||||
|
The `[dev_dependencies]` section contains a list of development dependencies for
|
||||||
|
the package. These are dependencies that are only required during development,
|
||||||
|
such as testing libraries or build tools. They are not installed when the
|
||||||
|
package is used by another package.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dev_dependencies]
|
||||||
|
foo = { name = "acme/foo", version = "1.2.3" }
|
||||||
|
```
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<LinkCard
|
||||||
|
title="Specifying Dependencies"
|
||||||
|
description="Learn more about specifying dependencies in pesde."
|
||||||
|
href="/guides/dependencies/"
|
||||||
|
/>
|
96
docs/src/content/docs/registry/policies.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
---
|
||||||
|
title: Policies
|
||||||
|
description: Policies for the pesde registry
|
||||||
|
---
|
||||||
|
|
||||||
|
The following policies apply to the [official public pesde registry](https://registry.pesde.daimond113.com)
|
||||||
|
and its related services, such as the index repository or websites.
|
||||||
|
They may not apply to other registries. By using the pesde registry, you agree
|
||||||
|
to these policies.
|
||||||
|
|
||||||
|
If anything is unclear, please [contact us](#contact-us), and we will be happy
|
||||||
|
to help.
|
||||||
|
|
||||||
|
## Contact Us
|
||||||
|
|
||||||
|
You can contact us at [pesde@daimond113.com](mailto:pesde@daimond113.com). In
|
||||||
|
case of a security issue, please prefix the subject with `[SECURITY]`.
|
||||||
|
|
||||||
|
## Permitted content
|
||||||
|
|
||||||
|
The pesde registry is a place for Luau-related packages. This includes:
|
||||||
|
|
||||||
|
- Libraries
|
||||||
|
- Frameworks
|
||||||
|
- Tools
|
||||||
|
|
||||||
|
The following content is forbidden:
|
||||||
|
|
||||||
|
- Malicious, vulnerable code
|
||||||
|
- Illegal, harmful content
|
||||||
|
- Miscellaneous files (doesn't include configuration files, documentation, etc.)
|
||||||
|
|
||||||
|
pesde is not responsible for the content of packages, the scope owner is. It
|
||||||
|
is the responsibility of the scope owner to ensure that the content of their
|
||||||
|
packages is compliant with the permitted content policy.
|
||||||
|
|
||||||
|
If you believe a package is breaking these requirements, please [contact us](#contact-us).
|
||||||
|
|
||||||
|
## Package removal
|
||||||
|
|
||||||
|
pesde does not support removing packages for reasons such as abandonment. A
|
||||||
|
package may only be removed for the following reasons:
|
||||||
|
|
||||||
|
- The package is breaking the permitted content policy
|
||||||
|
- The package contains security vulnerabilities
|
||||||
|
- The package must be removed for legal reasons (e.g. DMCA takedown)
|
||||||
|
|
||||||
|
In case a secret has been published to the registry, it must be invalidated.
|
||||||
|
If you believe a package should be removed, please [contact us](#contact-us).
|
||||||
|
We will review your request and take action if necessary.
|
||||||
|
|
||||||
|
If we find that a package is breaking the permitted content policy, we will
|
||||||
|
exercise our right to remove it from the registry without notice.
|
||||||
|
|
||||||
|
pesde reserves the right to remove any package from the registry at any time for
|
||||||
|
any or no reason, without notice.
|
||||||
|
|
||||||
|
## Package ownership
|
||||||
|
|
||||||
|
Packages are owned by scopes. Scope ownership is determined by the first person
|
||||||
|
to publish a package to the scope. The owner of the scope may send a pull request
|
||||||
|
to the index repository adding team members' user IDs to the scope's `scope.toml`
|
||||||
|
file to give them access to the scope, however at least one package must be
|
||||||
|
published to the scope before this can be done. The owner may also remove team
|
||||||
|
members from the scope.
|
||||||
|
|
||||||
|
A scope's true owner's ID must appear first in the `owners` field of the scope's
|
||||||
|
`scope.toml` file. Ownership may be transferred by the current owner sending a
|
||||||
|
pull request to the index repository, and the new owner confirming the transfer.
|
||||||
|
|
||||||
|
Only the owner may add or remove team members from the scope.
|
||||||
|
|
||||||
|
pesde reserves the right to override scope ownership in the case of a dispute,
|
||||||
|
such as if the original owner is unresponsive or multiple parties claim ownership.
|
||||||
|
|
||||||
|
## Scope squatting
|
||||||
|
|
||||||
|
Scope squatting is the act of creating a scope with the intent of preventing
|
||||||
|
others from using it, without any intention of using it yourself. This is
|
||||||
|
forbidden and can result in the removal (release) of the scope and its packages
|
||||||
|
from the registry without notice.
|
||||||
|
|
||||||
|
If you believe a scope is being squatted, please [contact us](#contact-us).
|
||||||
|
We will review your request and take action if necessary.
|
||||||
|
|
||||||
|
## API Usage
|
||||||
|
|
||||||
|
The pesde registry has an API for querying, downloading, and publishing packages.
|
||||||
|
Only non-malicious use is permitted. Malicious uses include:
|
||||||
|
|
||||||
|
- **Service Degradation**: this includes sending an excessive amount of requests
|
||||||
|
to the registry in order to degrade the service
|
||||||
|
- **Exploitation**: this includes trying to break the security of the registry
|
||||||
|
in order to gain unauthorized access
|
||||||
|
- **Harmful content**: this includes publishing harmful (non-law compliant,
|
||||||
|
purposefully insecure) content
|
2
docs/src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/// <reference path="../.astro/types.d.ts" />
|
||||||
|
/// <reference types="astro/client" />
|
11
docs/src/tailwind.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root[data-theme="light"] {
|
||||||
|
--sl-color-bg: rgb(255 245 230);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="light"] .sidebar-pane {
|
||||||
|
background-color: var(--sl-color-bg);
|
||||||
|
}
|
36
docs/tailwind.config.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import starlightPlugin from "@astrojs/starlight-tailwind"
|
||||||
|
import type { Config } from "tailwindcss"
|
||||||
|
import defaultTheme from "tailwindcss/defaultTheme"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||||
|
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Nunito Sans Variable", ...defaultTheme.fontFamily.sans],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
accent: {
|
||||||
|
200: "rgb(241 157 30)",
|
||||||
|
600: "rgb(120 70 10)",
|
||||||
|
900: "rgb(24 16 8)",
|
||||||
|
950: "rgb(10 7 4)",
|
||||||
|
},
|
||||||
|
gray: {
|
||||||
|
100: "rgb(245 230 210)",
|
||||||
|
200: "rgb(228 212 192)",
|
||||||
|
300: "rgb(198 167 140)",
|
||||||
|
400: "rgb(142 128 112)",
|
||||||
|
500: "rgb(84 70 50)",
|
||||||
|
600: "rgb(65 50 41)",
|
||||||
|
700: "rgb(50 42 35)",
|
||||||
|
800: "rgb(28 22 17)",
|
||||||
|
900: "rgb(10 7 4)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [starlightPlugin()],
|
||||||
|
} as Config
|
3
docs/tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict"
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ const ADDITIONAL_FORBIDDEN_FILES: &[&str] = &["default.project.json"];
|
||||||
struct DocEntryInfo {
|
struct DocEntryInfo {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, alias = "position")]
|
||||||
sidebar_position: Option<usize>,
|
sidebar_position: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
|
|
21
website/.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
1
website/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
4
website/.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Package Managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
14
website/.prettierrc
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": false,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
website/bun.lockb
Executable file
33
website/eslint.config.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import js from "@eslint/js"
|
||||||
|
import ts from "typescript-eslint"
|
||||||
|
import svelte from "eslint-plugin-svelte"
|
||||||
|
import prettier from "eslint-config-prettier"
|
||||||
|
import globals from "globals"
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs["flat/recommended"],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs["flat/prettier"],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.svelte"],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ["build/", ".svelte-kit/", "dist/"],
|
||||||
|
},
|
||||||
|
]
|
70
website/package.json
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "website",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/adapter-vercel": "^5.4.6",
|
||||||
|
"@sveltejs/kit": "^2.7.3",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
|
"@types/eslint": "^9.6.1",
|
||||||
|
"@types/gunzip-maybe": "^1.4.2",
|
||||||
|
"@types/tar-stream": "^3.1.3",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^9.13.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-svelte": "^2.46.0",
|
||||||
|
"globals": "^15.11.0",
|
||||||
|
"mdsvex": "^0.12.3",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"prettier-plugin-svelte": "^3.2.7",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||||
|
"svelte": "^5.1.4",
|
||||||
|
"svelte-check": "^4.0.5",
|
||||||
|
"tailwindcss": "^3.4.14",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"typescript-eslint": "^8.12.2",
|
||||||
|
"vite": "^5.4.10"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource-variable/nunito-sans": "^5.1.0",
|
||||||
|
"@shikijs/rehype": "^1.22.2",
|
||||||
|
"@types/hast": "^3.0.4",
|
||||||
|
"@types/unist": "^3.0.3",
|
||||||
|
"bits-ui": "next",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"gunzip-maybe": "^1.4.2",
|
||||||
|
"hast-util-heading": "^3.0.0",
|
||||||
|
"hast-util-heading-rank": "^3.0.0",
|
||||||
|
"hast-util-to-text": "^4.0.2",
|
||||||
|
"lucide-svelte": "^0.446.0",
|
||||||
|
"rehype-infer-description-meta": "^2.0.0",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
|
"rehype-sanitize": "^6.0.0",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
|
"rehype-stringify": "^10.0.1",
|
||||||
|
"remark-frontmatter": "^5.0.0",
|
||||||
|
"remark-gemoji": "^8.0.0",
|
||||||
|
"remark-gfm": "^4.0.0",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.1",
|
||||||
|
"shiki": "^1.22.2",
|
||||||
|
"tar-stream": "^3.1.7",
|
||||||
|
"unified": "^11.0.5",
|
||||||
|
"unist-util-map": "^4.0.0",
|
||||||
|
"vfile": "^6.0.3"
|
||||||
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"@shikijs/rehype@1.22.0": "patches/@shikijs%2Frehype@1.22.0.patch"
|
||||||
|
}
|
||||||
|
}
|
26
website/patches/@shikijs%2Frehype@1.22.0.patch
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@shikis/rehype doesn't use the `fallbackLanguage` if `lazy` is used.
|
||||||
|
|
||||||
|
--- a/dist/core.mjs
|
||||||
|
+++ b/dist/core.mjs
|
||||||
|
@@ -1,6 +1,8 @@
|
||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import { toString } from 'hast-util-to-string';
|
||||||
|
|
||||||
|
+import { bundledLanguages } from 'shiki';
|
||||||
|
+
|
||||||
|
const InlineCodeHandlers = {
|
||||||
|
"tailing-curly-colon": (_tree, node) => {
|
||||||
|
const raw = toString(node);
|
||||||
|
@@ -90,8 +92,12 @@ function rehypeShikiFromHighlighter(highlighter, options) {
|
||||||
|
const languageQueue = [];
|
||||||
|
const queue = [];
|
||||||
|
function getLanguage(lang) {
|
||||||
|
if (!lang)
|
||||||
|
return defaultLanguage;
|
||||||
|
+
|
||||||
|
+ if (!(lang in bundledLanguages))
|
||||||
|
+ return fallbackLanguage;
|
||||||
|
+
|
||||||
|
if (highlighter.getLoadedLanguages().includes(lang))
|
||||||
|
return lang;
|
||||||
|
if (lazy) {
|
6
website/postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
92
website/src/app.css
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
@import "tailwindcss/base";
|
||||||
|
@import "tailwindcss/components";
|
||||||
|
@import "tailwindcss/utilities";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-background: 255 245 230;
|
||||||
|
--color-card: 245 230 210;
|
||||||
|
--color-card-hover: 240 225 205;
|
||||||
|
--color-border: 200 180 160;
|
||||||
|
--color-header: 250 234 215;
|
||||||
|
|
||||||
|
--color-body: 84 70 50;
|
||||||
|
--color-heading: 70 55 35;
|
||||||
|
--color-light: 0 0 0;
|
||||||
|
|
||||||
|
--color-input-bg: 245 230 210;
|
||||||
|
--color-input-border: 180 160 140;
|
||||||
|
--color-placeholder: 130 90 40;
|
||||||
|
|
||||||
|
--color-primary: 120 70 10;
|
||||||
|
--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) {
|
||||||
|
:root {
|
||||||
|
--color-background: 10 7 4;
|
||||||
|
--color-card: 28 22 17;
|
||||||
|
--color-card-hover: 40 32 25;
|
||||||
|
--color-border: 28 22 17;
|
||||||
|
--color-header: 20 16 12;
|
||||||
|
|
||||||
|
--color-body: 198 167 140;
|
||||||
|
--color-heading: 227 213 200;
|
||||||
|
--color-light: 255 255 255;
|
||||||
|
|
||||||
|
--color-input-bg: 20 13 8;
|
||||||
|
--color-input-border: 78 60 40;
|
||||||
|
--color-placeholder: 169 147 128;
|
||||||
|
|
||||||
|
--color-primary: 241 157 30;
|
||||||
|
--color-primary-hover: 255 172 42;
|
||||||
|
--color-primary-bg: 241 157 30;
|
||||||
|
--color-primary-fg: 10 7 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-padding-top: theme(spacing.24);
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: theme(colors.background);
|
||||||
|
color: theme(colors.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursor-blink {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-scrollbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
13
website/src/app.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {}
|
12
website/src/app.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
website/src/lib/components/GitHub.svelte
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script lang="ts">
|
||||||
|
const props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg {...props} role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>GitHub</title>
|
||||||
|
<path
|
||||||
|
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
23
website/src/lib/components/Logo.svelte
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<svg {...$$restProps} viewBox="0 0 56 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>pesde</title>
|
||||||
|
<path
|
||||||
|
d="M0 28V26.3156H2.25652V12.2361H0.0635639V10.5517H4.44947L4.48125 11.9819L3.78205 12.3315C4.41769 11.6746 5.16986 11.1661 6.03857 10.8059C6.92846 10.4245 7.82895 10.2338 8.74003 10.2338C9.863 10.2338 10.88 10.4775 11.7911 10.9648C12.7234 11.4522 13.4544 12.1726 13.9841 13.126C14.5349 14.0795 14.8104 15.2448 14.8104 16.6221C14.8104 18.0416 14.5138 19.26 13.9205 20.277C13.3272 21.2728 12.5327 22.0356 11.5368 22.5653C10.5622 23.095 9.5028 23.3598 8.35865 23.3598C7.72301 23.3598 7.11916 23.2751 6.54708 23.1056C5.99619 22.9361 5.50887 22.7242 5.08511 22.4699C4.66135 22.1945 4.34353 21.8873 4.13165 21.5483L4.60838 21.4529L4.5766 26.3156H7.02381V28H0ZM7.94549 21.6118C9.19558 21.6118 10.2444 21.2092 11.0919 20.4041C11.9394 19.5778 12.3632 18.3807 12.3632 16.8127C12.3632 15.2872 11.9606 14.1113 11.1555 13.2849C10.3503 12.4586 9.3333 12.0454 8.1044 12.0454C7.72301 12.0454 7.26747 12.1196 6.73777 12.2679C6.20807 12.395 5.67837 12.6069 5.14867 12.9035C4.61898 13.2002 4.17403 13.5922 3.81383 14.0795L4.5766 12.7446L4.60838 20.7219L3.8774 19.7367C4.42828 20.3299 5.06392 20.7961 5.78431 21.1351C6.5047 21.4529 7.2251 21.6118 7.94549 21.6118Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M18.37 17.7448C17.0563 17.7448 15.8592 17.4694 14.7786 16.9185C13.7192 16.3676 12.8717 15.5942 12.236 14.5984C11.6216 13.6026 11.3144 12.4478 11.3144 11.1341C11.3144 9.77811 11.611 8.61277 12.2043 7.63813C12.7975 6.64229 13.5921 5.87953 14.5879 5.34983C15.5837 4.79894 16.6961 4.5235 17.925 4.5235C19.1963 4.5235 20.2875 4.78835 21.1986 5.31805C22.1308 5.84775 22.8406 6.59992 23.3279 7.57456C23.8153 8.54921 24.0589 9.69336 24.0589 11.007V11.6109H13.3802L13.412 10.1489H21.7388C21.7388 9.32257 21.5693 8.62337 21.2303 8.05129C20.9125 7.45803 20.4676 7.01308 19.8955 6.71645C19.3234 6.39863 18.6666 6.23972 17.925 6.23972C17.1411 6.23972 16.4313 6.43042 15.7956 6.8118C15.16 7.17199 14.6515 7.70169 14.2701 8.4009C13.9099 9.07891 13.7298 9.90524 13.7298 10.8799C13.7298 11.9181 13.9205 12.8186 14.3019 13.5814C14.6833 14.3229 15.213 14.9056 15.891 15.3294C16.5902 15.732 17.3847 15.9332 18.2746 15.9332C19.2281 15.9332 20.1074 15.7425 20.9125 15.3612C21.7177 14.9798 22.4169 14.503 23.0101 13.931L23.8365 15.3612C23.2644 16.018 22.5228 16.5795 21.6117 17.0456C20.7006 17.5117 19.6201 17.7448 18.37 17.7448Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M28.7199 22.6288C28.0631 22.6288 27.4275 22.5441 26.813 22.3746C26.2198 22.2051 25.7007 21.972 25.2557 21.6754C24.8108 21.3788 24.4718 21.0292 24.2387 20.6266L24.7154 20.9126V22.311H22.8721V17.9887H24.5247L24.7154 20.0545L24.2705 18.5608C24.5035 19.3447 25.0227 19.9486 25.8278 20.3723C26.6541 20.7961 27.5122 21.008 28.4021 21.008C29.2073 21.008 29.8747 20.8491 30.4044 20.5312C30.9553 20.2134 31.2307 19.7685 31.2307 19.1964C31.2307 18.5184 30.9977 18.0522 30.5315 17.798C30.0866 17.5225 29.3662 17.2789 28.3703 17.067L26.6223 16.6856C25.457 16.389 24.5353 15.997 23.8573 15.5097C23.1793 15.0012 22.8403 14.1642 22.8403 12.9989C22.8403 11.8759 23.2746 11.0178 24.1434 10.4245C25.0332 9.81009 26.135 9.50286 27.4487 9.50286C27.9572 9.50286 28.4869 9.57702 29.0378 9.72534C29.6098 9.85246 30.129 10.0538 30.5951 10.3292C31.0612 10.6046 31.3896 10.9436 31.5803 11.3462L31.1036 11.1873V9.75712H32.9787V14.0477H31.3261L30.9129 11.0284L31.4532 13.126C31.3684 12.7446 31.1248 12.4162 30.7222 12.1408C30.3408 11.8441 29.8853 11.6111 29.3556 11.4416C28.8471 11.2721 28.3386 11.1873 27.8301 11.1873C27.152 11.1873 26.5376 11.325 25.9867 11.6005C25.457 11.8759 25.1922 12.3209 25.1922 12.9353C25.1922 13.4015 25.3723 13.7617 25.7325 14.0159C26.1138 14.2702 26.7283 14.5033 27.5758 14.7151L29.1967 15.0647C30.0018 15.2554 30.7222 15.4885 31.3579 15.7639C32.0147 16.0182 32.5338 16.3996 32.9152 16.9081C33.2966 17.3954 33.4872 18.0946 33.4872 19.0057C33.4872 19.832 33.2542 20.5206 32.788 21.0715C32.3431 21.6012 31.7498 21.9932 31.0083 22.2475C30.2879 22.5017 29.5251 22.6288 28.7199 22.6288Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M37.1104 18.5607C35.9662 18.5607 34.9068 18.3064 33.9322 17.7979C32.9787 17.2682 32.2054 16.5054 31.6121 15.5096C31.0188 14.5138 30.7222 13.3272 30.7222 11.95C30.7222 10.5304 30.9977 9.34389 31.5485 8.39043C32.1206 7.41579 32.8728 6.67421 33.8051 6.1657C34.7373 5.65719 35.7544 5.40293 36.8561 5.40293C37.746 5.40293 38.6253 5.57243 39.494 5.91144C40.3839 6.22926 41.1679 6.6848 41.8459 7.27807L41.0831 7.5641V1.65266H38.8584V0H43.435V16.5266H45.4055V18.2111H41.0831V16.5584L41.8141 16.6855C41.1997 17.2788 40.5005 17.7449 39.7165 18.0839C38.9537 18.4018 38.085 18.5607 37.1104 18.5607ZM37.6189 16.7809C38.4452 16.7809 39.208 16.622 39.9072 16.3042C40.6276 15.9863 41.2844 15.5626 41.8777 15.0329L41.0831 16.1135V7.85014L41.8777 8.99429C41.2208 8.42221 40.4793 7.97727 39.6529 7.65945C38.8478 7.34163 38.0744 7.18272 37.3329 7.18272C36.5277 7.18272 35.8073 7.37341 35.1717 7.75479C34.5572 8.13618 34.0699 8.69766 33.7097 9.43924C33.3495 10.1596 33.1694 11.0601 33.1694 12.1407C33.1694 13.1366 33.3707 13.9841 33.7733 14.6833C34.1759 15.3613 34.7056 15.8804 35.3624 16.2406C36.0404 16.6008 36.7926 16.7809 37.6189 16.7809Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M50.3188 24.2004C49.0051 24.2004 47.808 23.925 46.7274 23.3741C45.668 22.8232 44.8205 22.0498 44.1848 21.054C43.5704 20.0582 43.2632 18.9034 43.2632 17.5898C43.2632 16.2337 43.5598 15.0684 44.1531 14.0937C44.7463 13.0979 45.5409 12.3351 46.5367 11.8054C47.5325 11.2545 48.6449 10.9791 49.8738 10.9791C51.1451 10.9791 52.2363 11.2439 53.1474 11.7736C54.0796 12.3033 54.7894 13.0555 55.2767 14.0302C55.7641 15.0048 56.0077 16.149 56.0077 17.4626V18.0665H45.329L45.3608 16.6045H53.6876C53.6876 15.7782 53.5181 15.079 53.1791 14.5069C52.8613 13.9136 52.4164 13.4687 51.8443 13.172C51.2722 12.8542 50.6154 12.6953 49.8738 12.6953C49.0899 12.6953 48.3801 12.886 47.7444 13.2674C47.1088 13.6276 46.6003 14.1573 46.2189 14.8565C45.8587 15.5345 45.6786 16.3609 45.6786 17.3355C45.6786 18.3737 45.8693 19.2742 46.2507 20.037C46.6321 20.7786 47.1617 21.3612 47.8398 21.785C48.539 22.1876 49.3335 22.3888 50.2234 22.3888C51.1769 22.3888 52.0562 22.1982 52.8613 21.8168C53.6665 21.4354 54.3657 20.9587 54.9589 20.3866L55.7853 21.8168C55.2132 22.4736 54.4716 23.0351 53.5605 23.5012C52.6494 23.9673 51.5688 24.2004 50.3188 24.2004Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
9
website/src/lib/components/Logomark.svelte
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<svg {...$$restProps} viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title>pesde</title>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M49.6025 0L92.9038 25V75L49.6025 100L6.30127 75V25L49.6025 0ZM14.3013 29.6188L49.6025 9.2376L84.9038 29.6188V70.3812L49.6025 90.7624L33.6148 81.5319V67.3848C34.5167 68.5071 35.6388 69.4215 36.981 70.1279C38.9701 71.148 41.0357 71.658 43.1779 71.658C46.442 71.658 49.1452 70.8929 51.2873 69.3629C53.4805 67.7818 55.1126 65.7672 56.1836 63.319C57.0915 61.3382 57.632 59.274 57.8054 57.1263C59.8723 57.7457 62.2157 58.0554 64.8356 58.0554C67.6918 58.0554 70.3695 57.6473 72.8686 56.8313C75.3678 55.9642 77.4079 54.8167 78.989 53.3886L75.7758 47.8038C74.5517 48.9258 72.9961 49.8439 71.109 50.5579C69.2219 51.221 67.2073 51.5525 65.0652 51.5525C61.3929 51.5525 58.6643 50.6854 56.8792 48.9513C56.7195 48.7962 56.567 48.6365 56.4217 48.472C55.6102 47.5539 55.0211 46.4896 54.6546 45.2791L54.6443 45.2452L54.669 45.2791H79.2185V41.9894C79.2185 39.0313 78.5555 36.3536 77.2294 33.9565C75.9543 31.5593 74.0927 29.6467 71.6445 28.2186C69.2474 26.7395 66.3657 26 62.9995 26C59.6843 26 56.8027 26.7395 54.3545 28.2186C51.9064 29.6467 50.0193 31.5593 48.6932 33.9565C47.6743 35.7983 47.0469 37.8057 46.8108 39.9788C45.6888 39.728 44.4778 39.6026 43.1779 39.6026C41.0357 39.6026 38.9701 40.1127 36.981 41.1327C35.3162 41.9651 33.9902 43.1549 33.0028 44.7023V40.3677H20.6855V46.2585H25.8113V77.0266L14.3013 70.3812V29.6188ZM55.1961 36.0986C54.6528 37.1015 54.3321 38.1216 54.234 39.1588H71.7976C71.7976 38.0367 71.4405 36.9401 70.7265 35.8691C70.0634 34.747 69.0689 33.8035 67.7428 33.0384C66.4677 32.2734 64.8867 31.8908 62.9995 31.8908C61.1124 31.8908 59.5058 32.2989 58.1798 33.1149C56.9047 33.88 55.9101 34.8745 55.1961 36.0986ZM49.6451 51.5692C49.3076 50.6641 48.8381 49.871 48.2367 49.1898C48.0885 49.0219 47.9323 48.8609 47.7681 48.7067C46.085 47.0746 44.0449 46.2585 41.6478 46.2585C40.1177 46.2585 38.6131 46.5645 37.134 47.1766C35.8594 47.6773 34.6863 48.5438 33.6148 49.7759V61.47C34.6863 62.6664 35.8594 63.5378 37.134 64.084C38.6131 64.6961 40.1177 65.0021 41.6478 65.0021C44.0449 65.0021 46.085 64.1861 47.7681 62.554C49.4512 60.9219 50.2928 58.6012 50.2928 55.5921C50.2928 54.0679 50.0769 52.727 49.6451 51.5692Z"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
74
website/src/lib/components/Select.svelte
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Select, type SelectSingleRootProps, type WithoutChildren } from "bits-ui"
|
||||||
|
import { Check, ChevronsUpDown } from "lucide-svelte"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
|
type Props = Omit<WithoutChildren<SelectSingleRootProps>, "type"> & {
|
||||||
|
placeholder?: string
|
||||||
|
items: { value: string; label: string; disabled?: boolean }[]
|
||||||
|
contentProps?: WithoutChildren<Select.ContentProps>
|
||||||
|
contentClass?: string
|
||||||
|
triggerClass?: string
|
||||||
|
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||||
|
sameWidth?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
value = $bindable(""),
|
||||||
|
items,
|
||||||
|
trigger,
|
||||||
|
contentProps,
|
||||||
|
contentClass = "",
|
||||||
|
triggerClass = "",
|
||||||
|
placeholder,
|
||||||
|
sameWidth = true,
|
||||||
|
id,
|
||||||
|
open = $bindable(false),
|
||||||
|
...restProps
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
|
const selectedLabel = $derived(items.find((item) => item.value === value)?.label)
|
||||||
|
const triggerLabel = $derived(selectedLabel ?? placeholder ?? "")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select.Root type="single" bind:value bind:open {...restProps}>
|
||||||
|
<Select.Trigger {id}>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
{#if trigger}
|
||||||
|
{@render trigger(props, triggerLabel)}
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
class={`border-input-border bg-input-bg ring-primary-bg/20 focus:border-primary relative flex h-11 w-full items-center rounded border px-4 outline-none ring-0 transition focus:ring-4 data-[disabled]:opacity-50 ${triggerClass}`}
|
||||||
|
>
|
||||||
|
{triggerLabel}
|
||||||
|
<ChevronsUpDown class="ml-auto size-5" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Portal>
|
||||||
|
<Select.Content
|
||||||
|
sideOffset={8}
|
||||||
|
collisionPadding={8}
|
||||||
|
{...contentProps}
|
||||||
|
class={`bg-card border-input-border z-50 max-h-[var(--bits-floating-available-height)] origin-top overflow-y-auto rounded-lg border p-1 shadow-lg ${sameWidth ? "w-[var(--bits-select-anchor-width)]" : ""} ${contentClass}`}
|
||||||
|
>
|
||||||
|
{#each items as { value, label, disabled } (value)}
|
||||||
|
<Select.Item
|
||||||
|
{value}
|
||||||
|
{label}
|
||||||
|
{disabled}
|
||||||
|
class="data-[highlighted]:bg-card-hover flex h-10 flex-shrink-0 items-center truncate rounded-sm px-3"
|
||||||
|
>
|
||||||
|
{#snippet children({ selected })}
|
||||||
|
{label}
|
||||||
|
{#if selected}
|
||||||
|
<Check class="ml-auto size-4" />
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Portal>
|
||||||
|
</Select.Root>
|
116
website/src/lib/markdown.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import rehypeShikiFromHighlighter from "@shikijs/rehype/core"
|
||||||
|
import type { Nodes } from "hast"
|
||||||
|
import { heading } from "hast-util-heading"
|
||||||
|
import { headingRank } from "hast-util-heading-rank"
|
||||||
|
import { toText } from "hast-util-to-text"
|
||||||
|
import rehypeInferDescriptionMeta from "rehype-infer-description-meta"
|
||||||
|
import rehypeRaw from "rehype-raw"
|
||||||
|
import rehypeSanitize from "rehype-sanitize"
|
||||||
|
import rehypeSlug from "rehype-slug"
|
||||||
|
import rehypeStringify from "rehype-stringify"
|
||||||
|
import remarkFrontmatter from "remark-frontmatter"
|
||||||
|
import remarkGemoji from "remark-gemoji"
|
||||||
|
import remarkGfm from "remark-gfm"
|
||||||
|
import remarkParse from "remark-parse"
|
||||||
|
import remarkRehype from "remark-rehype"
|
||||||
|
import { createCssVariablesTheme, createHighlighter } from "shiki"
|
||||||
|
import { unified } from "unified"
|
||||||
|
import type { Node } from "unist"
|
||||||
|
import { map } from "unist-util-map"
|
||||||
|
|
||||||
|
const highlighter = createHighlighter({
|
||||||
|
themes: [],
|
||||||
|
langs: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
export const markdown = (async () => {
|
||||||
|
return unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkFrontmatter)
|
||||||
|
.use(remarkGfm)
|
||||||
|
.use(remarkGemoji)
|
||||||
|
.use(remarkRehype, { allowDangerousHtml: true })
|
||||||
|
.use(rehypeRaw)
|
||||||
|
.use(rehypeSanitize)
|
||||||
|
.use(rehypeShikiFromHighlighter, await highlighter, {
|
||||||
|
lazy: true,
|
||||||
|
theme: createCssVariablesTheme({
|
||||||
|
name: "css-variables",
|
||||||
|
variablePrefix: "--shiki-",
|
||||||
|
variableDefaults: {},
|
||||||
|
fontStyle: true,
|
||||||
|
}),
|
||||||
|
fallbackLanguage: "text",
|
||||||
|
})
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.freeze()
|
||||||
|
})()
|
||||||
|
|
||||||
|
export type TocItem = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
level: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const docsMarkdown = (async () => {
|
||||||
|
return unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkFrontmatter)
|
||||||
|
.use(remarkGfm)
|
||||||
|
.use(remarkGemoji)
|
||||||
|
.use(remarkRehype, { allowDangerousHtml: true, clobberPrefix: "" })
|
||||||
|
.use(rehypeSlug)
|
||||||
|
.use(() => (node, file) => {
|
||||||
|
const toc: TocItem[] = []
|
||||||
|
file.data.toc = toc
|
||||||
|
|
||||||
|
return map(node as Nodes, (node) => {
|
||||||
|
if (node.type === "element" && node.tagName === "a") {
|
||||||
|
const fullUrl = new URL(node.properties.href as string, `file://${file.path}`)
|
||||||
|
|
||||||
|
let href = node.properties.href as string
|
||||||
|
if (fullUrl.protocol === "file:") {
|
||||||
|
href = file.data.basePath + fullUrl.pathname.replace(/\.mdx?$/, "") + fullUrl.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
properties: {
|
||||||
|
...node.properties,
|
||||||
|
href,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heading(node)) {
|
||||||
|
const rank = headingRank(node)
|
||||||
|
if (rank && typeof node.properties.id === "string" && rank >= 2 && rank <= 3) {
|
||||||
|
toc.push({
|
||||||
|
id: node.properties.id,
|
||||||
|
title: toText(node),
|
||||||
|
level: rank,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node
|
||||||
|
}) as Node
|
||||||
|
})
|
||||||
|
.use(rehypeRaw)
|
||||||
|
.use(rehypeSanitize)
|
||||||
|
.use(rehypeShikiFromHighlighter, await highlighter, {
|
||||||
|
lazy: true,
|
||||||
|
theme: createCssVariablesTheme({
|
||||||
|
name: "css-variables",
|
||||||
|
variablePrefix: "--shiki-",
|
||||||
|
variableDefaults: {},
|
||||||
|
fontStyle: true,
|
||||||
|
}),
|
||||||
|
fallbackLanguage: "text",
|
||||||
|
})
|
||||||
|
.use(rehypeInferDescriptionMeta, {
|
||||||
|
selector: "p",
|
||||||
|
})
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.freeze()
|
||||||
|
})()
|
106
website/src/lib/registry-api.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { PUBLIC_REGISTRY_URL } from "$env/static/public"
|
||||||
|
|
||||||
|
export type SearchResponse = {
|
||||||
|
count: number
|
||||||
|
data: PackageResponse[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PackageVersionsResponse = PackageResponse[]
|
||||||
|
|
||||||
|
export type PackageVersionResponse = PackageResponse
|
||||||
|
|
||||||
|
export type PackageResponse = {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
targets: TargetInfo[]
|
||||||
|
description: string
|
||||||
|
published_at: string
|
||||||
|
license?: string
|
||||||
|
authors?: string[]
|
||||||
|
repository?: string
|
||||||
|
dependencies: Record<string, DependencyEntry>
|
||||||
|
docs?: DocEntry[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TargetInfo = {
|
||||||
|
kind: TargetKind
|
||||||
|
lib: boolean
|
||||||
|
bin: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TargetKind = "roblox" | "roblox_server" | "lune" | "luau"
|
||||||
|
|
||||||
|
export type DependencyEntry = [DependencyInfo, DependencyKind]
|
||||||
|
|
||||||
|
export type DependencyInfo =
|
||||||
|
| {
|
||||||
|
index: string
|
||||||
|
name: string
|
||||||
|
target?: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
index: string
|
||||||
|
wally: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DependencyKind = "standard" | "peer" | "dev"
|
||||||
|
|
||||||
|
export type DocEntry = DocEntryCategory | DocEntryPage
|
||||||
|
|
||||||
|
export type DocEntryBase = {
|
||||||
|
label: string
|
||||||
|
position: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocEntryCategory = DocEntryBase & {
|
||||||
|
items?: DocEntry[]
|
||||||
|
collapsed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DocEntryPage = DocEntryBase & {
|
||||||
|
name: string
|
||||||
|
hash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TARGET_KIND_DISPLAY_NAMES: Record<TargetKind, string> = {
|
||||||
|
roblox: "Roblox",
|
||||||
|
roblox_server: "Roblox (server)",
|
||||||
|
lune: "Lune",
|
||||||
|
luau: "Luau",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEPENDENCY_KIND_DISPLAY_NAMES: Record<DependencyKind, string> = {
|
||||||
|
standard: "Dependencies",
|
||||||
|
peer: "Peer Dependencies",
|
||||||
|
dev: "Dev Dependencies",
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
8
website/src/routes/(app)/+error.svelte
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||||
|
<h1 class="mb-1 text-4xl font-bold text-heading">{$page.status}</h1>
|
||||||
|
<p class="text-lg">{$page.error?.message}</p>
|
||||||
|
</div>
|
14
website/src/routes/(app)/+layout.svelte
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script>
|
||||||
|
import Footer from "./Footer.svelte"
|
||||||
|
import Header from "./Header.svelte"
|
||||||
|
|
||||||
|
const { children } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main class="min-h-screen">
|
||||||
|
{@render children()}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
56
website/src/routes/(app)/+page.svelte
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { formatDistanceToNow } from "date-fns"
|
||||||
|
|
||||||
|
import { TARGET_KIND_DISPLAY_NAMES } from "$lib/registry-api"
|
||||||
|
import Hero from "./Hero.svelte"
|
||||||
|
|
||||||
|
const { data } = $props()
|
||||||
|
|
||||||
|
let displayDates = $state(false)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
displayDates = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hero />
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-screen-lg px-4 pb-32">
|
||||||
|
<h2 class="text-heading mb-4 text-2xl font-semibold">
|
||||||
|
<a id="recently-published" href="#recently-published">Recently Published</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{#each data.packages.slice(0, 24) as pkg}
|
||||||
|
{@const [scope, name] = pkg.name.split("/")}
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="bg-card hover:bg-card-hover relative overflow-hidden rounded px-5 py-4 transition"
|
||||||
|
>
|
||||||
|
<h3 class="truncate text-xl font-semibold">
|
||||||
|
<a href={`/packages/${pkg.name}`} class="after:absolute after:inset-0 after:content-['']">
|
||||||
|
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div class="text-primary mb-3 flex overflow-hidden whitespace-nowrap text-sm font-semibold">
|
||||||
|
<span class="truncate">v{pkg.version}</span>
|
||||||
|
<span class="whitespace-pre"
|
||||||
|
>{` · ${pkg.targets
|
||||||
|
.map((target) => TARGET_KIND_DISPLAY_NAMES[target.kind])
|
||||||
|
.join(", ")}`}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="mb-3 line-clamp-2 h-[2lh] overflow-hidden text-sm">{pkg.description}</p>
|
||||||
|
<div class="text-heading text-sm font-semibold">
|
||||||
|
<time datetime={pkg.published_at} class:invisible={!displayDates}>
|
||||||
|
{#if displayDates}
|
||||||
|
{formatDistanceToNow(new Date(pkg.published_at), { addSuffix: true })}
|
||||||
|
{:else}
|
||||||
|
...
|
||||||
|
{/if}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
10
website/src/routes/(app)/+page.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { fetchRegistryJson, type SearchResponse } from "$lib/registry-api"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ fetch }) => {
|
||||||
|
const { data: packages } = await fetchRegistryJson<SearchResponse>("search", fetch)
|
||||||
|
|
||||||
|
return {
|
||||||
|
packages,
|
||||||
|
}
|
||||||
|
}
|
17
website/src/routes/(app)/Footer.svelte
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<footer class="mx-auto max-w-screen-lg px-4">
|
||||||
|
<div class="border-t py-16 text-sm">
|
||||||
|
<p>Licensed under the MIT License.</p>
|
||||||
|
<p class="mb-4">Copyright © 2024 daimond113</p>
|
||||||
|
<p>
|
||||||
|
Designed with ♥︎ by
|
||||||
|
<a
|
||||||
|
href="https://github.com/lukadev-0"
|
||||||
|
class="font-semibold underline underline-offset-2"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
lukadev
|
||||||
|
</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
77
website/src/routes/(app)/Hamburger.svelte
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { navigating } from "$app/stores"
|
||||||
|
import GitHub from "$lib/components/GitHub.svelte"
|
||||||
|
import Logo from "$lib/components/Logo.svelte"
|
||||||
|
import { Dialog } from "bits-ui"
|
||||||
|
import { Menu, X } from "lucide-svelte"
|
||||||
|
import { fade, fly } from "svelte/transition"
|
||||||
|
import Search from "./Search.svelte"
|
||||||
|
|
||||||
|
let dialogOpen = $state(false)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($navigating) {
|
||||||
|
dialogOpen = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root bind:open={dialogOpen}>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<span class="sr-only">open menu</span>
|
||||||
|
<Menu aria-hidden="true" />
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Content forceMount>
|
||||||
|
{#snippet child({ props, open })}
|
||||||
|
{#if open}
|
||||||
|
<div {...props} class="fixed inset-0 top-0 z-50 flex flex-col">
|
||||||
|
<Dialog.Title class="sr-only">Menu</Dialog.Title>
|
||||||
|
<div transition:fade={{ duration: 200 }} class="bg-header">
|
||||||
|
<div class="relative z-50 flex h-14 flex-shrink-0 items-center justify-between px-4">
|
||||||
|
<a href="/">
|
||||||
|
<Logo class="text-primary h-7" />
|
||||||
|
</a>
|
||||||
|
<Dialog.Close>
|
||||||
|
<span class="sr-only">close menu</span>
|
||||||
|
<X aria-hidden="true" />
|
||||||
|
</Dialog.Close>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-1">
|
||||||
|
<Search />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-header flex flex-grow flex-col overflow-hidden"
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="flex h-full flex-col px-4 pt-2"
|
||||||
|
transition:fly={{ y: "-2%", duration: 200 }}
|
||||||
|
>
|
||||||
|
<div class="flex flex-grow flex-col border-y py-3">
|
||||||
|
{#snippet item(href: string, text: string)}
|
||||||
|
<a {href} class="hover:bg-card/80 flex h-10 items-center rounded px-3">{text}</a
|
||||||
|
>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{@render item("https://docs.pesde.daimond113.com/", "Documentation")}
|
||||||
|
{@render item("https://docs.pesde.daimond113.com/registry/policies", "Policies")}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center py-5">
|
||||||
|
<a
|
||||||
|
href="https://github.com/daimond113/pesde"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
<GitHub class="size-6" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
81
website/src/routes/(app)/Header.svelte
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import GitHub from "$lib/components/GitHub.svelte"
|
||||||
|
import Logo from "$lib/components/Logo.svelte"
|
||||||
|
import type { Action } from "svelte/action"
|
||||||
|
import Hamburger from "./Hamburger.svelte"
|
||||||
|
import Search from "./Search.svelte"
|
||||||
|
|
||||||
|
let hideSearch = $state(false)
|
||||||
|
let headerHidden = $state(false)
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
hideSearch = window.scrollY > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
handleScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerIntersection: Action = (node) => {
|
||||||
|
$effect(() => {
|
||||||
|
const callback: IntersectionObserverCallback = (entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
headerHidden = !entry.isIntersecting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(callback, {
|
||||||
|
threshold: 0,
|
||||||
|
rootMargin: "-57px 0px 0px 0px",
|
||||||
|
})
|
||||||
|
|
||||||
|
observer.observe(node)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:scroll={handleScroll} />
|
||||||
|
|
||||||
|
<header
|
||||||
|
use:headerIntersection
|
||||||
|
data-hidden={headerHidden ? true : null}
|
||||||
|
data-hide-search={hideSearch ? true : null}
|
||||||
|
class="bg-header group border-b pt-14 sm:!bg-transparent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-header fixed inset-x-0 top-0 z-50 bg-opacity-100 backdrop-blur-lg transition-[background] group-data-[hidden]:border-b group-data-[hidden]:bg-opacity-80 sm:!border-b sm:!bg-opacity-80"
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex h-14 max-w-screen-lg items-center justify-between px-4">
|
||||||
|
<a href="/">
|
||||||
|
<Logo class="text-primary h-7" />
|
||||||
|
</a>
|
||||||
|
<div class="hidden w-full max-w-80 sm:flex">
|
||||||
|
<Search />
|
||||||
|
</div>
|
||||||
|
<div class="[&_a:hover]:text-heading hidden items-center space-x-6 sm:flex [&_a]:transition">
|
||||||
|
<nav class="flex items-center space-x-6 font-medium">
|
||||||
|
<a href="https://docs.pesde.daimond113.com/">Docs</a>
|
||||||
|
<a href="https://docs.pesde.daimond113.com/registry/policies">Policies</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<a href="https://github.com/daimond113/pesde" target="_blank" rel="noreferrer noopener">
|
||||||
|
<GitHub class="size-6" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center sm:hidden">
|
||||||
|
<Hamburger />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden px-4 pb-2 pt-1 sm:hidden">
|
||||||
|
<div
|
||||||
|
class="transition duration-300 group-data-[hide-search]:-translate-y-1 group-data-[hide-search]:opacity-0"
|
||||||
|
>
|
||||||
|
<Search />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
90
website/src/routes/(app)/Hero.svelte
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
export const prerender = true
|
||||||
|
|
||||||
|
const tools = ["Luau", "Roblox", "Lune"]
|
||||||
|
|
||||||
|
let typewriteText = $state("Luau")
|
||||||
|
let blink = $state(true)
|
||||||
|
let cursorVisible = $state(false)
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
let current = 0
|
||||||
|
let timeout: NodeJS.Timeout
|
||||||
|
|
||||||
|
function typewrite(text: string) {
|
||||||
|
blink = false
|
||||||
|
|
||||||
|
let progress = 0
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
progress++
|
||||||
|
typewriteText = text.slice(0, progress)
|
||||||
|
|
||||||
|
if (progress >= text.length) {
|
||||||
|
blink = true
|
||||||
|
|
||||||
|
clearInterval(timeout)
|
||||||
|
timeout = setTimeout(() => clear(), 3500)
|
||||||
|
}
|
||||||
|
}, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
blink = false
|
||||||
|
|
||||||
|
let progress = typewriteText.length
|
||||||
|
|
||||||
|
timeout = setInterval(() => {
|
||||||
|
progress--
|
||||||
|
typewriteText = typewriteText.slice(0, progress)
|
||||||
|
|
||||||
|
if (progress <= 0) {
|
||||||
|
clearInterval(timeout)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
current++
|
||||||
|
if (current >= tools.length) current = 0
|
||||||
|
typewrite(tools[current])
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}, 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorVisible = true
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
clear()
|
||||||
|
}, 4500)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="mx-auto max-w-screen-lg px-4 py-32">
|
||||||
|
<h1 class="text-heading mb-3 text-2xl font-semibold md:mb-6 md:text-4xl lg:text-5xl">
|
||||||
|
Manage your packages<br />
|
||||||
|
<span class="sr-only"> for Luau</span>
|
||||||
|
<span class="text-primary" aria-hidden="true">
|
||||||
|
for {typewriteText}{#if cursorVisible}
|
||||||
|
<span
|
||||||
|
class="ml-1 inline-block h-[1.125rem] w-0.5 bg-current duration-100 md:h-7 lg:h-9"
|
||||||
|
class:animate-cursor-blink={blink}
|
||||||
|
></span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="mb-5 max-w-sm md:mb-8 md:max-w-md md:text-lg">
|
||||||
|
A package manager for the Luau programming language, supporting multiple runtimes including
|
||||||
|
Roblox and Lune.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://docs.pesde.daimond113.com/"
|
||||||
|
class="bg-primary-bg text-primary-fg hover:bg-primary-hover inline-flex h-10 items-center rounded px-4 font-semibold transition md:h-11 md:px-5"
|
||||||
|
>
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</section>
|
18
website/src/routes/(app)/Search.svelte
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Search } from "lucide-svelte"
|
||||||
|
import { searchQuery } from "./search-state.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form action="/search" class="w-full">
|
||||||
|
<label
|
||||||
|
class="relative flex h-9 w-full items-center overflow-hidden rounded border border-input-border bg-input-bg px-2 outline-none ring-0 ring-primary-bg/20 transition focus-within:border-primary focus-within:ring-4"
|
||||||
|
>
|
||||||
|
<Search class="size-5" aria-label="search" />
|
||||||
|
<input
|
||||||
|
name="q"
|
||||||
|
placeholder="search packages..."
|
||||||
|
class="absolute inset-0 bg-transparent pl-9 pr-2 text-heading placeholder:text-placeholder"
|
||||||
|
bind:value={searchQuery.value}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</form>
|
6
website/src/routes/(app)/[...404]/+page.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageLoad = () => {
|
||||||
|
error(404, "Not Found")
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import { formatDistanceToNow } from "date-fns"
|
||||||
|
import { onMount, setContext, untrack } from "svelte"
|
||||||
|
import Tab from "./Tab.svelte"
|
||||||
|
import TargetSelector from "./TargetSelector.svelte"
|
||||||
|
|
||||||
|
let { children, data } = $props()
|
||||||
|
|
||||||
|
const [scope, name] = $derived(data.pkg.name.split("/"))
|
||||||
|
|
||||||
|
let currentPkg = $state(data.pkg)
|
||||||
|
let currentTarget = $state(
|
||||||
|
data.pkg.targets.find((target) => target.kind === $page.params.target) ?? data.pkg.targets[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
setContext("currentPkg", {
|
||||||
|
get value() {
|
||||||
|
return currentPkg
|
||||||
|
},
|
||||||
|
set value(v) {
|
||||||
|
currentPkg = v
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setContext("currentTarget", {
|
||||||
|
get value() {
|
||||||
|
return currentTarget
|
||||||
|
},
|
||||||
|
set value(v) {
|
||||||
|
currentTarget = v
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getReadme = () => {
|
||||||
|
if ("target" in $page.params) {
|
||||||
|
return `${$page.params.version}/${$page.params.target}`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkgVersion = $derived(currentPkg.version)
|
||||||
|
const pkgDescription = $derived(currentPkg.description)
|
||||||
|
|
||||||
|
let pkgDate = $state<null | string>(null)
|
||||||
|
let readme = $state(getReadme())
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
pkgDate = formatDistanceToNow(new Date(currentPkg.published_at), { addSuffix: true })
|
||||||
|
readme = untrack(getReadme)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
return page.subscribe((page) => {
|
||||||
|
if (pkgDate === null || page.params.target !== undefined) {
|
||||||
|
currentTarget =
|
||||||
|
data.pkg.targets.find((target) => target.kind === page.params.target) ??
|
||||||
|
data.pkg.targets[0]
|
||||||
|
currentPkg = data.pkg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-prose px-4 py-16 lg:max-w-screen-lg">
|
||||||
|
<h1 class="text-3xl font-bold">
|
||||||
|
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="text-primary mb-2 font-semibold" class:invisible={pkgDate === null}>
|
||||||
|
v{pkgVersion} ·
|
||||||
|
<time datetime={data.pkg.published_at} title={new Date(data.pkg.published_at).toLocaleString()}>
|
||||||
|
published {pkgDate ?? "..."}
|
||||||
|
</time>
|
||||||
|
</div>
|
||||||
|
<p class="mb-6 max-w-prose">{pkgDescription}</p>
|
||||||
|
|
||||||
|
<div class="mb-8 lg:hidden">
|
||||||
|
<TargetSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="flex w-full flex-col sm:flex-row sm:border-b-2">
|
||||||
|
<Tab tab={readme}>Readme</Tab>
|
||||||
|
<Tab tab={`${pkgVersion}/${currentTarget.kind}/dependencies`}>Dependencies</Tab>
|
||||||
|
<Tab tab="versions">Versions</Tab>
|
||||||
|
{#if currentPkg.docs && currentPkg.docs.length > 0}
|
||||||
|
<Tab tab={`${pkgVersion}/${currentTarget.kind}/docs`}>Documentation</Tab>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
46
website/src/routes/(app)/packages/[scope]/[name]/+layout.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
fetchRegistryJson,
|
||||||
|
RegistryHttpError,
|
||||||
|
type PackageVersionResponse,
|
||||||
|
} from "$lib/registry-api"
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
import type { LayoutLoad } from "./$types"
|
||||||
|
|
||||||
|
type FetchPackageOptions = {
|
||||||
|
scope: string
|
||||||
|
name: string
|
||||||
|
version?: string
|
||||||
|
target?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPackage = async (fetcher: typeof fetch, options: FetchPackageOptions) => {
|
||||||
|
const { scope, name, version = "latest", target = "any" } = options
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fetchRegistryJson<PackageVersionResponse>(
|
||||||
|
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version}/${target}`,
|
||||||
|
fetcher,
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||||
|
error(404, "This package does not exist.")
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: LayoutLoad = async ({ params, fetch }) => {
|
||||||
|
const { scope, name, version, target } = params
|
||||||
|
|
||||||
|
if (version !== undefined && target === undefined) {
|
||||||
|
error(404, "Not Found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = version !== undefined ? { scope, name, version, target } : { scope, name }
|
||||||
|
|
||||||
|
const pkg = await fetchPackage(fetch, options)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pkg,
|
||||||
|
}
|
||||||
|
}
|
33
website/src/routes/(app)/packages/[scope]/[name]/Tab.svelte
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tab: string
|
||||||
|
children: Snippet
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tab, children }: Props = $props()
|
||||||
|
|
||||||
|
const basePath = $derived.by(() => {
|
||||||
|
const { scope, name } = $page.params
|
||||||
|
return `/packages/${scope}/${name}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeTab = $derived(
|
||||||
|
$page.url.pathname.slice(basePath.length).replace(/^\//, "").replace(/\/$/, ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
const href = $derived(`${basePath}/${tab}`)
|
||||||
|
const active = $derived(activeTab === tab)
|
||||||
|
|
||||||
|
const linkClass = $derived(
|
||||||
|
"font-semibold px-5 inline-flex items-center transition rounded-r h-12 border-l-2 " +
|
||||||
|
"sm:rounded-r-none sm:rounded-t sm:border-b-2 sm:border-l-0 sm:h-10 sm:-mb-0.5 " +
|
||||||
|
(active ? "text-primary border-primary bg-primary-bg/20" : "hover:bg-border/30"),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {href} class={linkClass}>
|
||||||
|
{@render children()}
|
||||||
|
</a>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation"
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import Select from "$lib/components/Select.svelte"
|
||||||
|
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo, type TargetKind } from "$lib/registry-api"
|
||||||
|
import { Label, useId } from "bits-ui"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const currentTarget = getContext<{ value: TargetInfo }>("currentTarget")
|
||||||
|
|
||||||
|
const basePath = $derived.by(() => {
|
||||||
|
const { scope, name } = $page.params
|
||||||
|
if ("target" in $page.params) {
|
||||||
|
const { version } = $page.params
|
||||||
|
return `/packages/${scope}/${name}/${version}`
|
||||||
|
}
|
||||||
|
return `/packages/${scope}/${name}/latest`
|
||||||
|
})
|
||||||
|
|
||||||
|
const items = ($page.data.pkg.targets as TargetInfo[]).map((target) => {
|
||||||
|
return {
|
||||||
|
value: target.kind,
|
||||||
|
label: TARGET_KIND_DISPLAY_NAMES[target.kind as TargetKind],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
|
||||||
|
let disabled = $state(false)
|
||||||
|
let open = $state(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="text-heading mb-1 text-lg font-semibold">
|
||||||
|
<Label.Root for={id} onclick={() => (open = true)}>Target</Label.Root>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
{items}
|
||||||
|
{disabled}
|
||||||
|
{id}
|
||||||
|
bind:open
|
||||||
|
name="target-selector"
|
||||||
|
allowDeselect={false}
|
||||||
|
value={currentTarget.value.kind}
|
||||||
|
triggerClass="mb-6"
|
||||||
|
onValueChange={(selected) => {
|
||||||
|
disabled = true
|
||||||
|
goto(`${basePath}/${selected}`).finally(() => {
|
||||||
|
disabled = false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
fetchRegistryJson,
|
||||||
|
RegistryHttpError,
|
||||||
|
type PackageVersionResponse,
|
||||||
|
type PackageVersionsResponse,
|
||||||
|
} from "$lib/registry-api"
|
||||||
|
import { error, redirect } from "@sveltejs/kit"
|
||||||
|
import type { LayoutLoad } from "./$types"
|
||||||
|
|
||||||
|
type FetchPackageOptions = {
|
||||||
|
scope: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
target: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchPackageAndVersions = async (fetcher: typeof fetch, options: FetchPackageOptions) => {
|
||||||
|
const { scope, name, version, target } = options
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [pkg, versions] = await Promise.all([
|
||||||
|
fetchRegistryJson<PackageVersionResponse>(
|
||||||
|
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version}/${target}`,
|
||||||
|
fetcher,
|
||||||
|
),
|
||||||
|
|
||||||
|
fetchRegistryJson<PackageVersionsResponse>(
|
||||||
|
`packages/${encodeURIComponent(`${scope}/${name}`)}`,
|
||||||
|
fetcher,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
versions.reverse()
|
||||||
|
return { pkg, versions }
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||||
|
error(404, "This package does not exist.")
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: LayoutLoad = async ({ params, url, fetch }) => {
|
||||||
|
const { scope, name, version, target } = params
|
||||||
|
|
||||||
|
if (version !== undefined && target === undefined) {
|
||||||
|
error(404, "Not Found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version === undefined || target === undefined || version === "latest" || target === "any") {
|
||||||
|
const pkg = await fetchRegistryJson<PackageVersionResponse>(
|
||||||
|
`packages/${encodeURIComponent(`${scope}/${name}`)}/${version ?? "latest"}/${target ?? "any"}`,
|
||||||
|
fetch,
|
||||||
|
)
|
||||||
|
|
||||||
|
const path = url.pathname.split("/").slice(6).join("/")
|
||||||
|
|
||||||
|
return redirect(303, `/packages/${scope}/${name}/${pkg.version}/${pkg.targets[0].kind}/${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pkg, versions } = await fetchPackageAndVersions(fetch, { scope, name, version, target })
|
||||||
|
|
||||||
|
return {
|
||||||
|
pkg,
|
||||||
|
versions: versions.map((v) => v.version),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import Logo from "$lib/components/Logo.svelte"
|
||||||
|
import Logomark from "$lib/components/Logomark.svelte"
|
||||||
|
import type { TocItem } from "$lib/markdown"
|
||||||
|
import { ChevronsUpDown } from "lucide-svelte"
|
||||||
|
import type { Action } from "svelte/action"
|
||||||
|
import Hamburger from "./Hamburger.svelte"
|
||||||
|
import TocMobile from "./MobileNavbar.svelte"
|
||||||
|
import Sidebar from "./Sidebar.svelte"
|
||||||
|
import Tab from "./Tab.svelte"
|
||||||
|
import TargetSelector from "./TargetSelector.svelte"
|
||||||
|
import Toc from "./Toc.svelte"
|
||||||
|
import TocObserver from "./TocObserver.svelte"
|
||||||
|
import VersionSelector from "./VersionSelector.svelte"
|
||||||
|
|
||||||
|
const { children, data } = $props()
|
||||||
|
const [scope, name] = data.pkg.name.split("/")
|
||||||
|
|
||||||
|
let hideNavigation = $state(false)
|
||||||
|
let headerHidden = $state(false)
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
hideNavigation = window.scrollY > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
handleScroll()
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerIntersection: Action = (node) => {
|
||||||
|
$effect(() => {
|
||||||
|
const callback: IntersectionObserverCallback = (entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
headerHidden = !entry.isIntersecting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(callback, {
|
||||||
|
threshold: 0,
|
||||||
|
rootMargin: "-57px 0px 0px 0px",
|
||||||
|
})
|
||||||
|
|
||||||
|
observer.observe(node)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toc: TocItem[] = $derived(
|
||||||
|
$page.error
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: "_top",
|
||||||
|
title: "Overview",
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: ($page.data.toc ?? []),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:scroll={handleScroll} />
|
||||||
|
|
||||||
|
<TocObserver toc={$page.data.toc ?? []} />
|
||||||
|
|
||||||
|
<div class="min-h-screen">
|
||||||
|
<header
|
||||||
|
class="bg-header group w-full border-b pt-14"
|
||||||
|
use:headerIntersection
|
||||||
|
data-hide-navigation={hideNavigation ? true : null}
|
||||||
|
data-hidden={headerHidden ? true : null}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-header fixed top-0 z-10 w-full backdrop-blur-lg transition-[background] group-data-[hidden]:border-b xl:group-data-[hidden]:bg-opacity-80"
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex h-14 max-w-screen-2xl items-center px-4">
|
||||||
|
{#snippet separator()}
|
||||||
|
<span class="text-body/60 px-2 text-xl">/</span>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<span class="flex min-w-0 items-center">
|
||||||
|
<a
|
||||||
|
class="flex min-w-0 items-center"
|
||||||
|
href={`/packages/${scope}/${name}/${$page.params.version ?? "latest"}/${$page.params.target ?? "any"}`}
|
||||||
|
>
|
||||||
|
<span class="text-primary mr-2">
|
||||||
|
<Logomark class="h-7" />
|
||||||
|
</span>
|
||||||
|
<span class="min-w-0 truncate">{scope}</span>
|
||||||
|
{@render separator()}
|
||||||
|
<span class="text-heading min-w-0 truncate font-medium">{name}</span>
|
||||||
|
</a>
|
||||||
|
<span class="hidden items-center lg:flex">
|
||||||
|
{#snippet trigger(props: Record<string, unknown>, label: string)}
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
class="flex items-center transition-opacity data-[disabled]:opacity-50"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
<ChevronsUpDown class="ml-1 size-4" />
|
||||||
|
</button>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
{@render separator()}
|
||||||
|
<VersionSelector {trigger} sameWidth={false} />
|
||||||
|
{@render separator()}
|
||||||
|
<TargetSelector {trigger} sameWidth={false} />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="ml-auto flex items-center lg:hidden">
|
||||||
|
<Hamburger />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="group-data-[hidden]:invisible">
|
||||||
|
<div class="-mb-px overflow-hidden pb-px">
|
||||||
|
<nav
|
||||||
|
class="transition duration-300 group-data-[hide-navigation]:-translate-y-1 group-data-[hide-navigation]:opacity-0"
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex max-w-screen-2xl px-4">
|
||||||
|
<Tab tab="docs" active={$page.data.activeTab == "docs"}>Docs</Tab>
|
||||||
|
<Tab tab="reference" active={$page.data.activeTab == "reference"}>Reference</Tab>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<TocMobile {toc} />
|
||||||
|
|
||||||
|
<div class="mx-auto flex max-w-screen-2xl">
|
||||||
|
<Sidebar items={$page.data.sidebar ?? []} />
|
||||||
|
<main class="mx-auto w-full max-w-screen-md px-4 md:px-6">
|
||||||
|
<div id="_top"></div>
|
||||||
|
{@render children()}
|
||||||
|
</main>
|
||||||
|
<Toc {toc} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="mx-auto max-w-screen-2xl px-4">
|
||||||
|
<div class="border-t py-16">
|
||||||
|
<p class="text-center font-semibold">
|
||||||
|
Documentation powered by<br />
|
||||||
|
<span class="mt-2 inline-block">
|
||||||
|
<a href="/"><Logo class="inline h-8" /></a>
|
||||||
|
<span class="mx-2 text-lg font-semibold">+</span>
|
||||||
|
<a href="https://eryn.io/moonwave">
|
||||||
|
<svg
|
||||||
|
class="inline h-6"
|
||||||
|
viewBox="0 0 76 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<title>Moonwave</title>
|
||||||
|
<path
|
||||||
|
d="M0.212789 31.8083C-0.187211 31.6083 0.012789 31.0083 0.512789 31.1083C6.01279 32.7083 15.9128 33.8083 21.4128 24.5083C28.8128 12.1083 31.8128 -5.59168 58.0128 1.70832C58.5128 1.80832 58.5128 2.50832 58.1128 2.60832C54.5128 4.10832 45.3128 9.20832 49.9128 19.5083C49.9128 19.5083 53.3128 8.50832 66.0128 11.8083C66.3128 11.9083 66.3128 12.3083 66.0128 12.5083C63.5128 13.6083 56.5128 17.6083 59.1128 25.8083C61.4128 33.0083 71.8128 32.5083 75.1128 32.1083C75.6128 32.0083 75.7128 32.6083 75.1128 32.9083C72.2128 34.3083 56.7128 40.1083 50.3128 26.9083C50.3128 26.9083 41.9128 32.5083 37.5128 21.1083C37.3128 21.2083 25.7128 45.0083 0.212789 31.8083Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
|
@ -0,0 +1,101 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { navigating, page } from "$app/stores"
|
||||||
|
import Logomark from "$lib/components/Logomark.svelte"
|
||||||
|
import { Dialog, Label, useId } from "bits-ui"
|
||||||
|
import { Menu, X } from "lucide-svelte"
|
||||||
|
import { fade, fly } from "svelte/transition"
|
||||||
|
import SidebarItem from "./SidebarItem.svelte"
|
||||||
|
import TargetSelector from "./TargetSelector.svelte"
|
||||||
|
import VersionSelector from "./VersionSelector.svelte"
|
||||||
|
|
||||||
|
let dialogOpen = $state(false)
|
||||||
|
const [scope, name] = $page.data.pkg.name.split("/")
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($navigating) {
|
||||||
|
dialogOpen = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let versionOpen = $state(false)
|
||||||
|
let targetOpen = $state(false)
|
||||||
|
|
||||||
|
const versionId = useId()
|
||||||
|
const targetId = useId()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root bind:open={dialogOpen}>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<span class="sr-only">open menu</span>
|
||||||
|
<Menu aria-hidden="true" />
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Content forceMount>
|
||||||
|
{#snippet child({ props, open })}
|
||||||
|
{#if open}
|
||||||
|
<div {...props} class="fixed inset-0 top-0 z-50 flex flex-col">
|
||||||
|
<Dialog.Title class="sr-only">Menu</Dialog.Title>
|
||||||
|
<div transition:fade={{ duration: 200 }} class="bg-header">
|
||||||
|
<div
|
||||||
|
class="relative z-50 flex h-14 flex-shrink-0 items-center justify-between border-b px-4"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="flex items-center truncate"
|
||||||
|
href={`/packages/${scope}/${name}/${$page.params.version ?? "latest"}/${$page.params.target ?? "any"}`}
|
||||||
|
>
|
||||||
|
{#snippet separator()}
|
||||||
|
<span class="text-body/60 px-2 text-xl">/</span>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<span class="text-primary mr-2">
|
||||||
|
<Logomark class="h-7" />
|
||||||
|
</span>
|
||||||
|
<span class="truncate">{scope}</span>
|
||||||
|
{@render separator()}
|
||||||
|
<span class="text-heading truncate font-medium">{name}</span>
|
||||||
|
</a>
|
||||||
|
<Dialog.Close>
|
||||||
|
<span class="sr-only">close menu</span>
|
||||||
|
<X aria-hidden="true" />
|
||||||
|
</Dialog.Close>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="bg-header flex flex-grow flex-col overflow-hidden"
|
||||||
|
transition:fade={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
class="flex h-full flex-col overflow-y-auto p-4"
|
||||||
|
transition:fly={{ y: "-2%", duration: 200 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mb-4 flex flex-col space-y-4 border-b pb-4 sm:flex-row sm:space-x-4 sm:space-y-0"
|
||||||
|
>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="text-heading mb-1 text-sm font-semibold">
|
||||||
|
<Label.Root for={versionId}>Version</Label.Root>
|
||||||
|
</div>
|
||||||
|
<VersionSelector id={versionId} bind:open={versionOpen} />
|
||||||
|
</div>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="text-heading mb-1 text-sm font-semibold">
|
||||||
|
<Label.Root for={targetId}>Target</Label.Root>
|
||||||
|
</div>
|
||||||
|
<TargetSelector id={targetId} bind:open={targetOpen} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{#each $page.data.sidebar ?? [] as item}
|
||||||
|
<li>
|
||||||
|
<SidebarItem {item} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TocItem } from "$lib/markdown"
|
||||||
|
import { Popover } from "bits-ui"
|
||||||
|
import { ChevronRight } from "lucide-svelte"
|
||||||
|
import { scale } from "svelte/transition"
|
||||||
|
import TocList, { tocScroll } from "./TocList.svelte"
|
||||||
|
import { activeHeaderId } from "./TocObserver.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
toc: TocItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { toc }: Props = $props()
|
||||||
|
|
||||||
|
let activeHeaderTitle = $derived(toc.find((item) => item.id === activeHeaderId.value)?.title)
|
||||||
|
let popoverOpen = $state(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="bg-header/80 sticky top-14 border-b backdrop-blur-lg lg:ml-72 xl:hidden">
|
||||||
|
<div class="mx-auto flex h-12 max-w-screen-md items-center px-4 md:px-6">
|
||||||
|
<Popover.Root bind:open={popoverOpen}>
|
||||||
|
<Popover.Trigger
|
||||||
|
class="bg-background/80 flex h-8 flex-shrink-0 items-center rounded border px-4 text-sm font-semibold {popoverOpen
|
||||||
|
? 'border-primary'
|
||||||
|
: 'hover:border-body/50'}"
|
||||||
|
>
|
||||||
|
On this page
|
||||||
|
<ChevronRight class="ml-0.5 size-4" />
|
||||||
|
</Popover.Trigger>
|
||||||
|
<Popover.Content side="bottom" align="start" sideOffset={4} collisionPadding={8}>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<div
|
||||||
|
class="bg-card border-input-border max-h-[var(--bits-popover-content-available-height)] w-72 max-w-[var(--bits-popover-content-available-width)] origin-top-left overflow-y-auto rounded border p-4 pl-2 pr-6 shadow-lg"
|
||||||
|
use:tocScroll
|
||||||
|
transition:scale={{ start: 0.95, duration: 200 }}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<TocList mobile {toc} />
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
|
||||||
|
{#if activeHeaderTitle}
|
||||||
|
<span class="text-heading ml-2 truncate text-sm">{activeHeaderTitle}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</nav>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
export type SidebarItem =
|
||||||
|
| {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
label: string
|
||||||
|
children: SidebarItem[]
|
||||||
|
collapsed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// this comment is here to fix the syntax highlighting
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import SidebarItemComponent from "./SidebarItem.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: SidebarItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items }: Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="sticky top-14 -mt-12 hidden h-[calc(100vh-theme(spacing.14))] w-72 flex-shrink-0 flex-col overflow-y-auto border-r p-4 lg:flex xl:mt-0"
|
||||||
|
>
|
||||||
|
<ul>
|
||||||
|
{#each items as item}
|
||||||
|
<li>
|
||||||
|
<SidebarItemComponent {item} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import { ChevronDownIcon } from "lucide-svelte"
|
||||||
|
import type { SidebarItem } from "./Sidebar.svelte"
|
||||||
|
import Self from "./SidebarItem.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: SidebarItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const { item }: Props = $props()
|
||||||
|
|
||||||
|
let open = $state("collapsed" in item ? !item.collapsed : true)
|
||||||
|
|
||||||
|
let active = $derived.by(() => {
|
||||||
|
if ("href" in item) {
|
||||||
|
const fullUrl = new URL(item.href, $page.url)
|
||||||
|
return fullUrl.pathname === $page.url.pathname
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if "href" in item}
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
class={`-mx-2 block rounded px-2 py-1.5 text-sm transition ${active ? "bg-primary-bg/20 text-primary font-semibold" : "hover:text-heading"}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<details class="group flex flex-col py-0.5" bind:open>
|
||||||
|
<summary class="text-heading flex list-none items-center py-1 font-bold">
|
||||||
|
<span>{item.label} </span>
|
||||||
|
<ChevronDownIcon class="ml-auto size-5 transition group-[:not([open])]:-rotate-90" />
|
||||||
|
</summary>
|
||||||
|
<ul class="mb-1 border-l pl-4">
|
||||||
|
{#each item.children as child}
|
||||||
|
<li>
|
||||||
|
<Self item={child} />
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
{/if}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
tab: string
|
||||||
|
children: Snippet
|
||||||
|
active?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tab, children, active = false }: Props = $props()
|
||||||
|
|
||||||
|
const basePath = $derived.by(() => {
|
||||||
|
const { scope, name, version, target } = $page.params
|
||||||
|
return `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const href = $derived(`${basePath}/${tab}`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a
|
||||||
|
{href}
|
||||||
|
class={`-mb-px flex h-9 w-full items-center justify-center border-b-2 px-4 font-semibold transition sm:w-auto ${active ? "border-primary text-heading" : "hover:border-border hover:text-heading border-transparent"}`}
|
||||||
|
>
|
||||||
|
{@render children()}
|
||||||
|
</a>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation"
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import Select from "$lib/components/Select.svelte"
|
||||||
|
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo } from "$lib/registry-api"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
|
let disabled = $state(false)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||||
|
sameWidth?: boolean
|
||||||
|
open?: boolean
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let { trigger, sameWidth = true, open = $bindable(false), id }: Props = $props()
|
||||||
|
|
||||||
|
const basePath = $derived.by(() => {
|
||||||
|
const { scope, name } = $page.params
|
||||||
|
return `/packages/${scope}/${name}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
items={$page.data.pkg.targets.map((target: TargetInfo) => ({
|
||||||
|
value: target.kind,
|
||||||
|
label: TARGET_KIND_DISPLAY_NAMES[target.kind],
|
||||||
|
}))}
|
||||||
|
value={$page.params.target ?? $page.data.pkg.targets[0].kind}
|
||||||
|
contentClass={sameWidth ? "" : "w-32"}
|
||||||
|
onValueChange={(target) => {
|
||||||
|
disabled = true
|
||||||
|
|
||||||
|
const path = $page.data.activeTab === "docs" ? "docs/intro" : "reference"
|
||||||
|
|
||||||
|
goto(`${basePath}/${$page.data.pkg.version}/${target}/${path}`).finally(() => {
|
||||||
|
disabled = false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
bind:open
|
||||||
|
{disabled}
|
||||||
|
{sameWidth}
|
||||||
|
{trigger}
|
||||||
|
{id}
|
||||||
|
/>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TocItem } from "$lib/markdown"
|
||||||
|
import TocList, { tocScroll } from "./TocList.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
toc: TocItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { toc }: Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside
|
||||||
|
class="hide-scrollbar sticky top-14 hidden h-[calc(100vh-theme(spacing.14))] w-72 flex-shrink-0 overflow-y-auto py-16 text-lg xl:block"
|
||||||
|
use:tocScroll
|
||||||
|
>
|
||||||
|
<nav class="border-l pr-4 text-sm">
|
||||||
|
<h2 class="text-heading mb-1 pl-4 text-base font-semibold">On this page</h2>
|
||||||
|
<TocList {toc} />
|
||||||
|
</nav>
|
||||||
|
</aside>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
import type { Action } from "svelte/action"
|
||||||
|
|
||||||
|
export const tocScroll: Action<HTMLElement> = (node) => {
|
||||||
|
$effect(() => {
|
||||||
|
const link = node.querySelector(`[data-item-id="${activeHeaderId.value}"]`)
|
||||||
|
|
||||||
|
if (link && link instanceof HTMLElement) {
|
||||||
|
setTimeout(() => {
|
||||||
|
node.scrollTo({
|
||||||
|
top: link.offsetTop + link.clientHeight - node.clientHeight / 2,
|
||||||
|
behavior: "smooth",
|
||||||
|
})
|
||||||
|
}, 20)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TocItem } from "$lib/markdown"
|
||||||
|
import { activeHeaderId } from "./TocObserver.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
toc: TocItem[]
|
||||||
|
mobile?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const { toc, mobile = false }: Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{#each toc as item}
|
||||||
|
{@const active = item.id === activeHeaderId.value}
|
||||||
|
<li data-item-id={item.id}>
|
||||||
|
<a
|
||||||
|
href={`#${item.id}`}
|
||||||
|
class={`-ml-px block truncate pl-[calc(var(--level)*theme(spacing.4))] transition ${active ? "text-primary" : "hover:text-heading"} ${mobile ? "py-2" : "py-1"}`}
|
||||||
|
style:--level={item.level - 1}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script module lang="ts">
|
||||||
|
let _activeHeaderId = $state("")
|
||||||
|
|
||||||
|
export const activeHeaderId = {
|
||||||
|
get value() {
|
||||||
|
return _activeHeaderId
|
||||||
|
},
|
||||||
|
|
||||||
|
set value(value) {
|
||||||
|
_activeHeaderId = value
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TocItem } from "$lib/markdown"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
toc: TocItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { toc }: Props = $props()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
toc
|
||||||
|
|
||||||
|
const isHeading = (el: Element): boolean => {
|
||||||
|
if (el.id === "_top") return true
|
||||||
|
if (el instanceof HTMLHeadingElement) {
|
||||||
|
const level = el.tagName[1]
|
||||||
|
if (level) {
|
||||||
|
const int = parseInt(level, 10)
|
||||||
|
if (int >= 2 && int <= 3) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNearestHeading = (el: Element | null): Element | null => {
|
||||||
|
if (!el) return null
|
||||||
|
|
||||||
|
while (el) {
|
||||||
|
if (isHeading(el)) return el
|
||||||
|
el = el.previousElementSibling
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback: IntersectionObserverCallback = (entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) continue
|
||||||
|
|
||||||
|
const heading = getNearestHeading(entry.target)
|
||||||
|
if (!heading) continue
|
||||||
|
|
||||||
|
const item = toc.find((item) => {
|
||||||
|
if (item.id === "_top" && heading.id === "_top") return true
|
||||||
|
return `user-content-${item.id}` === heading.id
|
||||||
|
})
|
||||||
|
if (item) {
|
||||||
|
activeHeaderId.value = item.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(callback, {
|
||||||
|
rootMargin: "-56px 0px -80% 0px",
|
||||||
|
})
|
||||||
|
|
||||||
|
const targets = document.querySelectorAll("main > *")
|
||||||
|
for (const target of targets) {
|
||||||
|
observer.observe(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation"
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import Select from "$lib/components/Select.svelte"
|
||||||
|
import { fetchRegistryJson, type PackageVersionResponse } from "$lib/registry-api"
|
||||||
|
import type { Snippet } from "svelte"
|
||||||
|
|
||||||
|
let disabled = $state(false)
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
trigger?: Snippet<[Record<string, unknown>, string]>
|
||||||
|
sameWidth?: boolean
|
||||||
|
open?: boolean
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let { trigger, sameWidth = true, open = $bindable(false), id }: Props = $props()
|
||||||
|
|
||||||
|
const basePath = $derived.by(() => {
|
||||||
|
const { scope, name } = $page.params
|
||||||
|
return `/packages/${scope}/${name}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
items={$page.data.versions.map((v: string) => ({ value: v, label: v }))}
|
||||||
|
value={$page.data.pkg.version}
|
||||||
|
contentClass={sameWidth ? "" : "w-32"}
|
||||||
|
onValueChange={(version) => {
|
||||||
|
disabled = true
|
||||||
|
|
||||||
|
const path = $page.data.activeTab === "docs" ? "docs/intro" : "reference"
|
||||||
|
|
||||||
|
fetchRegistryJson<PackageVersionResponse>(
|
||||||
|
`packages/${encodeURIComponent($page.data.pkg.name)}/${version}/any`,
|
||||||
|
fetch,
|
||||||
|
)
|
||||||
|
.then((pkg) => goto(`${basePath}/${version}/${pkg.targets[0].kind}/${path}`))
|
||||||
|
.finally(() => {
|
||||||
|
disabled = false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
bind:open
|
||||||
|
{disabled}
|
||||||
|
{sameWidth}
|
||||||
|
{trigger}
|
||||||
|
{id}
|
||||||
|
/>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||||
|
<h1 class="text-heading mb-1 text-4xl font-bold">{$page.status}</h1>
|
||||||
|
<p class="text-lg">{$page.error?.message}</p>
|
||||||
|
</div>
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { DocEntry } from "$lib/registry-api"
|
||||||
|
import type { SidebarItem } from "../Sidebar.svelte"
|
||||||
|
import type { LayoutLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: LayoutLoad = async ({ params, parent }) => {
|
||||||
|
const parentData = await parent()
|
||||||
|
|
||||||
|
const { scope, name, version, target } = params
|
||||||
|
const basePath = `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||||
|
|
||||||
|
function docEntryToSidebarItem(entry: DocEntry): SidebarItem {
|
||||||
|
if ("name" in entry) {
|
||||||
|
return {
|
||||||
|
label: entry.label,
|
||||||
|
href: `${basePath}/docs/${entry.name}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: entry.label,
|
||||||
|
children: entry.items?.map(docEntryToSidebarItem) ?? [],
|
||||||
|
collapsed: entry.collapsed ?? false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebar = parentData.pkg.docs?.map(docEntryToSidebarItem) ?? []
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeTab: "docs",
|
||||||
|
doc: params.doc,
|
||||||
|
sidebar,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { redirect } from "@sveltejs/kit"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageLoad = async () => {
|
||||||
|
redirect(301, "docs/intro")
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script lang="ts">
|
||||||
|
const { data } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="prose max-w-none py-16">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html data.html}
|
||||||
|
</main>
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { docsMarkdown, type TocItem } from "$lib/markdown"
|
||||||
|
import { fetchRegistry, RegistryHttpError, type DocEntry } from "$lib/registry-api"
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
import { VFile } from "vfile"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
const findDocTitle = (docs: DocEntry[], name: string): string | undefined => {
|
||||||
|
for (const doc of docs) {
|
||||||
|
if ("name" in doc && doc.name === name) return doc.label
|
||||||
|
if ("items" in doc && doc.items) {
|
||||||
|
const title = findDocTitle(doc.items, name)
|
||||||
|
if (title) return title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params, parent, fetch }) => {
|
||||||
|
try {
|
||||||
|
const page = await fetchRegistry(
|
||||||
|
`packages/${encodeURIComponent(`${params.scope}/${params.name}`)}/${params.version ?? "latest"}/${params.target ?? "any"}?doc=${encodeURIComponent(params.doc)}`,
|
||||||
|
fetch,
|
||||||
|
).then((r) => r.text())
|
||||||
|
|
||||||
|
const inputFile = new VFile({
|
||||||
|
path: `/docs/${params.doc}`,
|
||||||
|
value: page,
|
||||||
|
data: {
|
||||||
|
basePath: `/packages/${params.scope}/${params.name}/${params.version ?? "latest"}/${params.target ?? "any"}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const file = await (await docsMarkdown).process(inputFile)
|
||||||
|
|
||||||
|
const html = file.value
|
||||||
|
|
||||||
|
const parentData = await parent()
|
||||||
|
const docTitle = findDocTitle(parentData.pkg.docs ?? [], params.doc) ?? params.doc
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
toc: [
|
||||||
|
{
|
||||||
|
id: "_top",
|
||||||
|
title: "Overview",
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
...(file.data.toc as TocItem[]),
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
siteName: `${parentData.pkg.name} - pesde`,
|
||||||
|
title: docTitle,
|
||||||
|
description: (file.data.meta as { description: string }).description,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||||
|
error(404, "Page not found")
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { LayoutLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: LayoutLoad = async ({ params, parent }) => {
|
||||||
|
const { scope, name, version, target } = params
|
||||||
|
const basePath = `/packages/${scope}/${name}/${version ?? "latest"}/${target ?? "any"}`
|
||||||
|
|
||||||
|
const parentData = await parent()
|
||||||
|
return {
|
||||||
|
activeTab: "reference",
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
label: "Reference",
|
||||||
|
href: `${basePath}/reference`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
toc: [
|
||||||
|
{
|
||||||
|
id: "_top",
|
||||||
|
title: "Overview",
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
siteName: `${parentData.pkg.name} - pesde`,
|
||||||
|
title: "Reference",
|
||||||
|
description: `API reference for ${parentData.pkg.name}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script>
|
||||||
|
import { Boxes } from "lucide-svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main class="mx-auto max-w-screen-2xl px-4 py-48 text-center">
|
||||||
|
<Boxes aria-hidden="true" class="mx-auto mb-8 size-16" />
|
||||||
|
|
||||||
|
<h1 class="text-heading mb-1 text-xl font-bold">Reference coming soon!</h1>
|
||||||
|
<p>The reference will contain API documentation for packages.</p>
|
||||||
|
</main>
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import GitHub from "$lib/components/GitHub.svelte"
|
||||||
|
import type { TargetInfo } from "$lib/registry-api"
|
||||||
|
import { BinaryIcon, Globe, Icon, LibraryIcon, Mail } from "lucide-svelte"
|
||||||
|
import type { ComponentType } from "svelte"
|
||||||
|
import TargetSelector from "../../TargetSelector.svelte"
|
||||||
|
import Command from "./Command.svelte"
|
||||||
|
|
||||||
|
let { children, data } = $props()
|
||||||
|
|
||||||
|
const installCommand = $derived(`pesde add ${data.pkg.name}`)
|
||||||
|
const xCommand = $derived(`pesde x ${data.pkg.name}`)
|
||||||
|
|
||||||
|
const defaultTarget = $derived(
|
||||||
|
"target" in $page.params && $page.params.target !== "any"
|
||||||
|
? $page.params.target
|
||||||
|
: data.pkg.targets[0].kind,
|
||||||
|
)
|
||||||
|
const currentTarget = $derived(
|
||||||
|
data.pkg.targets.find((target: TargetInfo) => target.kind === defaultTarget),
|
||||||
|
)
|
||||||
|
|
||||||
|
const repositoryUrl = $derived(
|
||||||
|
data.pkg.repository !== undefined ? new URL(data.pkg.repository) : undefined,
|
||||||
|
)
|
||||||
|
const isGitHub = $derived(repositoryUrl?.hostname === "github.com")
|
||||||
|
const githubRepo = $derived(
|
||||||
|
repositoryUrl?.pathname
|
||||||
|
.split("/")
|
||||||
|
.slice(1, 3)
|
||||||
|
.join("/")
|
||||||
|
.replace(/\.git$/, ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
const exportNames: Partial<Record<keyof TargetInfo, string>> = {
|
||||||
|
lib: "Library",
|
||||||
|
bin: "Binary",
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportIcons: Partial<Record<keyof TargetInfo, ComponentType<Icon>>> = {
|
||||||
|
lib: LibraryIcon,
|
||||||
|
bin: BinaryIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportEntries = $derived(
|
||||||
|
Object.entries(exportNames).filter(([key]) => !!currentTarget?.[key as keyof TargetInfo]),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col lg:flex-row">
|
||||||
|
<div class="flex-grow lg:pr-4">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
<aside
|
||||||
|
class="w-full flex-shrink-0 border-t pt-16 lg:ml-auto lg:max-w-[22rem] lg:border-l lg:border-t-0 lg:pl-4 lg:pt-6"
|
||||||
|
>
|
||||||
|
<h2 class="text-heading mb-1 text-lg font-semibold">Install</h2>
|
||||||
|
<Command command={installCommand} class="mb-4" />
|
||||||
|
|
||||||
|
<div class="hidden lg:block">
|
||||||
|
<TargetSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if data.pkg.license !== undefined}
|
||||||
|
<h2 class="text-heading mb-1 text-lg font-semibold">License</h2>
|
||||||
|
<div class="mb-6">{data.pkg.license}</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if data.pkg.repository !== undefined}
|
||||||
|
<h2 class="text-heading mb-1 text-lg font-semibold">Repository</h2>
|
||||||
|
<div class="mb-6">
|
||||||
|
<a
|
||||||
|
href={data.pkg.repository}
|
||||||
|
class="inline-flex items-center space-x-2 underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
{#if isGitHub}
|
||||||
|
<GitHub class="text-primary size-5" />
|
||||||
|
<span>
|
||||||
|
{githubRepo}
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
{data.pkg.repository}
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<h2 class="text-heading mb-1 text-lg font-semibold">Exports</h2>
|
||||||
|
<ul class="mb-6 space-y-0.5">
|
||||||
|
{#each exportEntries as [exportKey, exportName]}
|
||||||
|
{@const Icon = exportIcons[exportKey as keyof TargetInfo]}
|
||||||
|
<li class="flex items-center">
|
||||||
|
<Icon aria-hidden="true" class="text-primary mr-2 size-5" />
|
||||||
|
{exportName}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{#if currentTarget?.bin}
|
||||||
|
<p class="text-body/80 -mt-3 mb-4 text-sm">
|
||||||
|
This package provides a binary that can be executed after installation, or globally via:
|
||||||
|
</p>
|
||||||
|
<Command command={xCommand} class="mb-6" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if data.pkg.authors && data.pkg.authors.length > 0}
|
||||||
|
<h2 class="text-heading mb-2 text-lg font-semibold">Authors</h2>
|
||||||
|
<ul>
|
||||||
|
{#each data.pkg.authors as author}
|
||||||
|
{@const [, name] = author.match(/^(.*?)\s*(<|\(|$)/) ?? []}
|
||||||
|
{@const [, email] = author.match(/<(.*)>/) ?? []}
|
||||||
|
{@const [, website] = author.match(/\((.*)\)/) ?? []}
|
||||||
|
|
||||||
|
<li class="mb-2 flex items-center">
|
||||||
|
{name}
|
||||||
|
<div class="ml-auto flex items-center space-x-2">
|
||||||
|
{#if email}
|
||||||
|
<a href={`mailto:${email}`} class="text-primary ml-1" title={`Email: ${email}`}>
|
||||||
|
<Mail class="text-primary size-5" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{#if website}
|
||||||
|
<a href={website} class="text-primary ml-1" title={`Website: ${website}`}>
|
||||||
|
<Globe class="text-primary size-5" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</aside>
|
||||||
|
</div>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
const { data } = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="prose min-w-0 py-8 prose-pre:w-full prose-pre:overflow-auto">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html data.readmeHtml}
|
||||||
|
</div>
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { markdown } from "$lib/markdown"
|
||||||
|
import { fetchRegistry, RegistryHttpError } from "$lib/registry-api"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
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: PageLoad = async ({ parent, fetch }) => {
|
||||||
|
const { pkg } = await parent()
|
||||||
|
const { name, version, targets } = pkg
|
||||||
|
|
||||||
|
const readmeText = await fetchReadme(fetch, name, version, targets[0].kind)
|
||||||
|
|
||||||
|
const file = await (await markdown)
|
||||||
|
.process(readmeText)
|
||||||
|
|
||||||
|
const readmeHtml = file.value
|
||||||
|
|
||||||
|
return {
|
||||||
|
readmeHtml,
|
||||||
|
pkg,
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
title: `${name} - ${version}`,
|
||||||
|
description: pkg.description,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Check, Clipboard } from "lucide-svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
command: string
|
||||||
|
class?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const { command, class: classname = "" }: Props = $props()
|
||||||
|
|
||||||
|
let didCopy = $state(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={`flex h-11 items-center overflow-hidden rounded border text-sm ${classname}`}>
|
||||||
|
<code class="truncate px-4">{command}</code>
|
||||||
|
<button
|
||||||
|
class="bg-card/40 hover:bg-card/60 ml-auto flex size-11 items-center justify-center border-l"
|
||||||
|
onclick={() => {
|
||||||
|
navigator.clipboard.writeText(command)
|
||||||
|
|
||||||
|
if (didCopy) return
|
||||||
|
|
||||||
|
didCopy = true
|
||||||
|
setTimeout(() => {
|
||||||
|
didCopy = false
|
||||||
|
}, 1000)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="sr-only">Copy</span>
|
||||||
|
{#if didCopy}
|
||||||
|
<Check class="size-5" aria-hidden="true" />
|
||||||
|
{:else}
|
||||||
|
<Clipboard class="size-5" aria-hidden="true" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
import { DEPENDENCY_KIND_DISPLAY_NAMES, type DependencyKind } from "$lib/registry-api.js"
|
||||||
|
|
||||||
|
const { data } = $props()
|
||||||
|
|
||||||
|
// Vercel only supports up to Node 20.x, which doesn't support Object.groupBy
|
||||||
|
function groupBy<T, K extends PropertyKey>(
|
||||||
|
arr: T[],
|
||||||
|
predicate: (value: T) => K,
|
||||||
|
): Partial<Record<K, T[]>> {
|
||||||
|
const groups: Partial<Record<K, T[]>> = {}
|
||||||
|
for (const item of arr) {
|
||||||
|
const key = predicate(item)
|
||||||
|
if (key in groups) {
|
||||||
|
groups[key]!.push(item)
|
||||||
|
} else {
|
||||||
|
groups[key] = [item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupedDeps = $derived(
|
||||||
|
groupBy(
|
||||||
|
Object.entries(data.pkg.dependencies).map(([alias, dependency]) => ({ alias, dependency })),
|
||||||
|
(entry) => entry.dependency[1],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const stripWally = (s: string) => s.replace(/^wally#/, "")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if Object.keys(groupedDeps).length === 0}
|
||||||
|
<p class="py-24 text-center">This package doesn't have any dependencies.</p>
|
||||||
|
{:else}
|
||||||
|
<div class="space-y-8 py-8">
|
||||||
|
{#each Object.entries(groupedDeps).sort( (a, b) => b[0].localeCompare(a[0]), ) as [dependencyKind, group]}
|
||||||
|
<section>
|
||||||
|
<h2 class="text-heading mb-4 text-xl font-medium">
|
||||||
|
{DEPENDENCY_KIND_DISPLAY_NAMES[dependencyKind as DependencyKind]}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
{#each group as { dependency: [dependencyInfo] }}
|
||||||
|
{@const isWally = "wally" in dependencyInfo}
|
||||||
|
{@const [scope, name] = (
|
||||||
|
isWally ? stripWally(dependencyInfo.wally) : dependencyInfo.name
|
||||||
|
).split("/")}
|
||||||
|
{@const target = isWally
|
||||||
|
? undefined
|
||||||
|
: (dependencyInfo.target ?? $page.params.target ?? data.pkg.targets[0].kind)}
|
||||||
|
{@const isOfficialRegistry = isWally
|
||||||
|
? dependencyInfo.index.toLowerCase() === "https://github.com/upliftgames/wally-index"
|
||||||
|
: dependencyInfo.index.toLowerCase() === "https://github.com/daimond113/pesde-index"}
|
||||||
|
|
||||||
|
<article
|
||||||
|
class={`bg-card relative overflow-hidden rounded px-5 py-4 transition ${
|
||||||
|
isOfficialRegistry ? "hover:bg-card-hover" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<h3 class="font-semibold">
|
||||||
|
<svelte:element
|
||||||
|
this={isOfficialRegistry ? "a" : "svelte:fragment"}
|
||||||
|
{...isOfficialRegistry
|
||||||
|
? {
|
||||||
|
href: isWally
|
||||||
|
? `https://wally.run/package/${stripWally(dependencyInfo.wally)}`
|
||||||
|
: `/packages/${dependencyInfo.name}/latest/${target}`,
|
||||||
|
}
|
||||||
|
: {}}
|
||||||
|
class="after:absolute after:inset-0 after:content-['']"
|
||||||
|
>
|
||||||
|
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||||
|
{#if isWally}
|
||||||
|
<span class="text-red-400">(wally)</span>
|
||||||
|
{/if}
|
||||||
|
</svelte:element>
|
||||||
|
</h3>
|
||||||
|
<div class="text-primary text-sm font-semibold">
|
||||||
|
{dependencyInfo.version}
|
||||||
|
{#if !isWally}
|
||||||
|
·
|
||||||
|
{target}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ parent }) => {
|
||||||
|
const data = await parent()
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta: {
|
||||||
|
title: `${data.pkg.name} - ${data.pkg.version} - dependencies`,
|
||||||
|
description: data.pkg.description,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { TARGET_KIND_DISPLAY_NAMES, type TargetInfo } from "$lib/registry-api.js"
|
||||||
|
import { formatDistanceToNow } from "date-fns"
|
||||||
|
|
||||||
|
const { data } = $props()
|
||||||
|
|
||||||
|
let displayDates = $state(false)
|
||||||
|
$effect(() => {
|
||||||
|
displayDates = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="space-y-4 py-4">
|
||||||
|
{#each data.versions as pkg, index}
|
||||||
|
{@const isLatest = index === 0}
|
||||||
|
|
||||||
|
<article
|
||||||
|
class={`bg-card hover:bg-card-hover relative overflow-hidden rounded px-5 py-4 transition ${
|
||||||
|
isLatest ? "ring-primary ring-2 ring-inset" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<h2 class="text-heading font-semibold">
|
||||||
|
<a
|
||||||
|
href={`/packages/${pkg.name}/${pkg.version}/any`}
|
||||||
|
class="after:absolute after:inset-0 after:content-['']"
|
||||||
|
>
|
||||||
|
{pkg.version}
|
||||||
|
{#if isLatest}
|
||||||
|
<span class="text-primary">(latest)</span>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="text-sm font-semibold" class:invisible={!displayDates}>
|
||||||
|
<time datetime={pkg.published_at}>
|
||||||
|
{#if displayDates}
|
||||||
|
{formatDistanceToNow(new Date(pkg.published_at), { addSuffix: true })}
|
||||||
|
{:else}
|
||||||
|
...
|
||||||
|
{/if}
|
||||||
|
</time>
|
||||||
|
·
|
||||||
|
{pkg.targets.map((target: TargetInfo) => TARGET_KIND_DISPLAY_NAMES[target.kind]).join(", ")}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
fetchRegistryJson,
|
||||||
|
RegistryHttpError,
|
||||||
|
type PackageVersionsResponse,
|
||||||
|
} from "$lib/registry-api"
|
||||||
|
import { error } from "@sveltejs/kit"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params, fetch }) => {
|
||||||
|
const { scope, name } = params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const versions = await fetchRegistryJson<PackageVersionsResponse>(
|
||||||
|
`packages/${encodeURIComponent(`${scope}/${name}`)}`,
|
||||||
|
fetch,
|
||||||
|
)
|
||||||
|
|
||||||
|
versions.reverse()
|
||||||
|
|
||||||
|
return {
|
||||||
|
versions,
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
title: `${versions[0].name} - versions`,
|
||||||
|
description: versions[0].description,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RegistryHttpError && e.response.status === 404) {
|
||||||
|
error(404, "Package not found")
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
10
website/src/routes/(app)/search-state.svelte.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
let _searchQuery = $state("")
|
||||||
|
|
||||||
|
export const searchQuery = {
|
||||||
|
get value() {
|
||||||
|
return _searchQuery
|
||||||
|
},
|
||||||
|
set value(value: string) {
|
||||||
|
_searchQuery = value
|
||||||
|
},
|
||||||
|
}
|
169
website/src/routes/(app)/search/+page.svelte
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation"
|
||||||
|
import { TARGET_KIND_DISPLAY_NAMES } from "$lib/registry-api.js"
|
||||||
|
import { Pagination } from "bits-ui"
|
||||||
|
import { formatDistanceToNow } from "date-fns"
|
||||||
|
import { ChevronLeft, ChevronRight, SearchX } from "lucide-svelte"
|
||||||
|
|
||||||
|
const { data } = $props()
|
||||||
|
|
||||||
|
let displayDates = $state(false)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
displayDates = true
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-lg px-4">
|
||||||
|
{#await data.result}
|
||||||
|
<div class="sr-only">Loading...</div>
|
||||||
|
<div
|
||||||
|
class="my-8 flex flex-col sm:flex-row sm:items-center sm:justify-between"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="bg-card-hover w-20 animate-pulse rounded text-transparent">...</div>
|
||||||
|
<div class="bg-card-hover mx-auto mt-8 h-8 w-48 animate-pulse rounded sm:m-0"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-8 space-y-4">
|
||||||
|
{#each Array.from({ length: 10 }).map((_, i) => i) as i}
|
||||||
|
<div class="bg-card/50 overflow-hidden rounded px-5 py-4">
|
||||||
|
<div class="mb-1 flex items-center justify-between">
|
||||||
|
<div class="bg-card-hover w-52 animate-pulse rounded text-xl text-transparent">...</div>
|
||||||
|
<div
|
||||||
|
class="bg-card-hover hidden w-32 animate-pulse rounded text-sm text-transparent sm:block"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="bg-card-hover mb-3 w-96 max-w-full animate-pulse rounded text-sm text-transparent"
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-card-hover w-16 max-w-full animate-pulse rounded text-sm text-transparent">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="bg-card-hover mx-auto my-8 h-8 w-48 animate-pulse rounded"></div>
|
||||||
|
{:then result}
|
||||||
|
{#if result.data.length === 0}
|
||||||
|
<div class="flex flex-col items-center py-32 text-center">
|
||||||
|
<SearchX class="mb-6 size-16" />
|
||||||
|
<h1 class="text-heading font-bold">No results</h1>
|
||||||
|
<p class="text-balance">We didn't find any packages matching your search query.</p>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
{#snippet pagination()}
|
||||||
|
<Pagination.Root
|
||||||
|
count={result.count}
|
||||||
|
page={data.page}
|
||||||
|
perPage={data.pageSize}
|
||||||
|
onPageChange={(page) => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set("q", data.query)
|
||||||
|
params.set("page", page.toString())
|
||||||
|
|
||||||
|
goto(`/search?${params}`)
|
||||||
|
}}
|
||||||
|
let:pages
|
||||||
|
>
|
||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<Pagination.PrevButton
|
||||||
|
class="hover:enabled:bg-card-hover inline-flex size-8 items-center justify-center rounded transition disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<ChevronLeft />
|
||||||
|
</Pagination.PrevButton>
|
||||||
|
{#each pages as page (page.key)}
|
||||||
|
{#if page.type === "ellipsis"}
|
||||||
|
<div class="px-2">...</div>
|
||||||
|
{:else}
|
||||||
|
<Pagination.Page
|
||||||
|
{page}
|
||||||
|
class="hover:bg-card-hover hover:text-heading data-[selected]:bg-primary-bg data-[selected]:text-primary-fg inline-flex size-8 items-center justify-center rounded font-bold transition"
|
||||||
|
>
|
||||||
|
{page.value}
|
||||||
|
</Pagination.Page>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<Pagination.NextButton
|
||||||
|
class="hover:enabled:bg-card-hover inline-flex size-8 items-center justify-center rounded transition disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<ChevronRight />
|
||||||
|
</Pagination.NextButton>
|
||||||
|
</div>
|
||||||
|
</Pagination.Root>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<div class="my-8 flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<h1 class="font-bold">{result.count} {result.count > 1 ? "results" : "result"}</h1>
|
||||||
|
<div class="mx-auto mt-8 sm:m-0">
|
||||||
|
{@render pagination()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-8 space-y-4">
|
||||||
|
{#each result.data as pkg}
|
||||||
|
{@const [scope, name] = pkg.name.split("/")}
|
||||||
|
|
||||||
|
{#snippet timeAndVersion()}
|
||||||
|
<time datetime={pkg.published_at}>
|
||||||
|
{#if displayDates}
|
||||||
|
{formatDistanceToNow(new Date(pkg.published_at), { addSuffix: true })}
|
||||||
|
{:else}
|
||||||
|
...
|
||||||
|
{/if}
|
||||||
|
</time>
|
||||||
|
<span>{" · "}</span>
|
||||||
|
<span class="truncate">v{pkg.version}</span>
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
|
<article
|
||||||
|
class="bg-card hover:bg-card-hover relative overflow-hidden rounded px-5 py-4 transition"
|
||||||
|
>
|
||||||
|
<div class="mb-1 flex items-center justify-between">
|
||||||
|
<h3 class="truncate text-xl font-semibold">
|
||||||
|
<a
|
||||||
|
href={`/packages/${pkg.name}`}
|
||||||
|
class="after:absolute after:inset-0 after:content-['']"
|
||||||
|
>
|
||||||
|
<span class="text-heading">{scope}/</span><span class="text-light">{name}</span>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
class="text-heading hidden text-sm font-semibold sm:block"
|
||||||
|
class:invisible={!displayDates}
|
||||||
|
>
|
||||||
|
{@render timeAndVersion()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mb-3 h-[1lh] overflow-hidden truncate text-sm">{pkg.description}</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={`text-primary text-sm font-bold ${displayDates ? "" : "invisible sm:visible"}`}
|
||||||
|
>
|
||||||
|
{pkg.targets.map((target) => TARGET_KIND_DISPLAY_NAMES[target.kind]).join(", ")}
|
||||||
|
<span class="sm:hidden">
|
||||||
|
<span>{" · "}</span>
|
||||||
|
{@render timeAndVersion()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto my-8 max-w-min">
|
||||||
|
{@render pagination()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:catch error}
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||||
|
<h1 class="text-heading mb-1 text-4xl font-bold">Error</h1>
|
||||||
|
<p class="text-lg">{error.message}</p>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
31
website/src/routes/(app)/search/+page.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { fetchRegistryJson, type SearchResponse } from "$lib/registry-api"
|
||||||
|
import type { PageLoad } from "./$types"
|
||||||
|
|
||||||
|
const PAGE_SIZE = 50
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ fetch, url }) => {
|
||||||
|
const query = url.searchParams.get("q") ?? ""
|
||||||
|
|
||||||
|
let page = parseInt(url.searchParams.get("page") ?? "1")
|
||||||
|
if (isNaN(page) || page < 1) {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set("query", query)
|
||||||
|
params.set("offset", String((page - 1) * PAGE_SIZE))
|
||||||
|
|
||||||
|
const result = fetchRegistryJson<SearchResponse>(`search?${params}`, fetch)
|
||||||
|
|
||||||
|
return {
|
||||||
|
query,
|
||||||
|
page,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
result,
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
title: query === "" ? "search" : `results for "${query}"`,
|
||||||
|
description: "Search for packages in the pesde registry.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
8
website/src/routes/+error.svelte
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-screen-xl px-4 py-32 text-center">
|
||||||
|
<h1 class="text-heading mb-1 text-4xl font-bold">{$page.status}</h1>
|
||||||
|
<p class="text-lg">{$page.error?.message}</p>
|
||||||
|
</div>
|
78
website/src/routes/+layout.svelte
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores"
|
||||||
|
|
||||||
|
import "@fontsource-variable/nunito-sans"
|
||||||
|
import "../app.css"
|
||||||
|
|
||||||
|
const { children } = $props()
|
||||||
|
|
||||||
|
const siteName = $derived($page.data.meta?.siteName ?? "pesde")
|
||||||
|
const title = $derived($page.data.meta?.title)
|
||||||
|
const description = $derived(
|
||||||
|
$page.data.meta?.description ??
|
||||||
|
"A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune.",
|
||||||
|
)
|
||||||
|
|
||||||
|
let themeColor = $state("#F19D1E")
|
||||||
|
$effect(() => {
|
||||||
|
const query = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
|
||||||
|
const updateColor = (dark: boolean) => {
|
||||||
|
themeColor = dark ? "#14100C" : "#FAEAD7"
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = (e: MediaQueryListEvent) => {
|
||||||
|
updateColor(e.matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addEventListener("change", listener)
|
||||||
|
updateColor(query.matches)
|
||||||
|
|
||||||
|
return () => query.removeEventListener("change", listener)
|
||||||
|
})
|
||||||
|
|
||||||
|
function hashChange() {
|
||||||
|
let hash
|
||||||
|
|
||||||
|
try {
|
||||||
|
hash = decodeURIComponent(location.hash.slice(1)).toLowerCase()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = "user-content-" + hash
|
||||||
|
const target = document.getElementById(id)
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
target.scrollIntoView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
hashChange()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{title ? `${title} - ${siteName}` : siteName}</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<meta name="theme-color" content={themeColor} />
|
||||||
|
|
||||||
|
<meta property="og:site_name" content={siteName} />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content={title ?? "Manage your packages for Luau"} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:image" content="/favicon-48x48.png" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="/favicon-48x48.png" sizes="48x48" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="pesde" />
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<svelte:window onhashchange={hashChange} />
|
||||||
|
|
||||||
|
{@render children()}
|