diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dea1dd50..002838c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,3 +89,31 @@ jobs: name: playwright-report path: playwright-report/ retention-days: 30 + + # Builds a freshly scaffolded consumer project against the local package. + # This exercises the installed-from-npm code path (JSX transform for + # node_modules sources, CJS interop, bundled assets, dependency pins) that + # the workspace build cannot see. + scaffold: + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - uses: pnpm/action-setup@v4 + - name: Scaffold a project against the local package + run: | + node packages/starpod/cli/index.mjs new /tmp/scaffold-smoke --yes --name "Scaffold Smoke" --rss "https://rss.flightcast.com/w7bqgc792i30fd43a32uawx0.xml" + node -e "const fs=require('fs');const f='/tmp/scaffold-smoke/package.json';const p=JSON.parse(fs.readFileSync(f));p.dependencies.starpod='file:'+process.env.GITHUB_WORKSPACE+'/packages/starpod';fs.writeFileSync(f,JSON.stringify(p,null,2))" + - name: Build the scaffolded project + working-directory: /tmp/scaffold-smoke + run: | + pnpm install + pnpm exec astro build + - name: Verify agent endpoints in the output + run: | + test -f /tmp/scaffold-smoke/.vercel/output/static/llms.txt + test -f /tmp/scaffold-smoke/.vercel/output/static/openapi.json + node -e "const c=require('/tmp/scaffold-smoke/.vercel/output/config.json');const n=c.routes.filter(r=>JSON.stringify(r).includes('text/markdown')).length;if(!n)throw new Error('no negotiation routes in config.json');console.log('negotiation routes:',n)" diff --git a/.github/workflows/plan-release.yml b/.github/workflows/plan-release.yml new file mode 100644 index 00000000..b2843ad3 --- /dev/null +++ b/.github/workflows/plan-release.yml @@ -0,0 +1,61 @@ +name: Plan Release +on: + workflow_dispatch: + push: + branches: + - main + - master + pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + types: + - labeled + - unlabeled + +concurrency: + group: plan-release # only the latest one of these should ever be running + cancel-in-progress: true + +jobs: + should-run-release-plan-prepare: + name: Should we run release-plan prepare? + runs-on: ubuntu-latest + outputs: + should-prepare: ${{ steps.should-prepare.outputs.should-prepare }} + steps: + - uses: release-plan/actions/should-prepare-release@v1 + with: + ref: 'main' + id: should-prepare + + create-prepare-release-pr: + name: Create Prepare Release PR + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: should-run-release-plan-prepare + permissions: + contents: write + issues: read + pull-requests: write + if: needs.should-run-release-plan-prepare.outputs.should-prepare == 'true' + steps: + - uses: release-plan/actions/prepare@v1 + name: Run release-plan prepare + with: + ref: 'main' + env: + GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} + id: explanation + + - uses: peter-evans/create-pull-request@v8 + name: Create Prepare Release PR + with: + commit-message: "Prepare Release ${{ steps.explanation.outputs.new-version}} using 'release-plan'" + labels: "internal" + sign-commits: true + branch: release-preview + title: Prepare Release ${{ steps.explanation.outputs.new-version }} + body: | + This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR πŸ‘ + + ----------------------------------------- + + ${{ steps.explanation.outputs.text }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..77725c0b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,40 @@ +# For every push to the primary branch with .release-plan.json modified, +# runs release-plan. + +name: Publish Stable + +on: + workflow_dispatch: + push: + branches: + - main + - master + paths: + - '.release-plan.json' + +concurrency: + group: publish-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + publish: + name: "NPM Publish" + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + attestations: write + + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v5 + - uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: 'https://registry.npmjs.org' + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Publish to NPM + run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish + env: + GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..825c32f0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/README.md b/README.md index 8c51e0d3..8c8a7e2d 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,48 @@ Starpod is the easiest way to create a podcast website in 5 minutes or less and it is 100% free and open source. +It ships as an [Astro integration](./packages/starpod): install the `starpod` +package, point it at your RSS feed, and it generates your whole site β€” episode +pages, a persistent audio player, search, transcripts with clickable +timestamps, and a suite of agent-friendly endpoints. Updating your site is +`pnpm update`, like any other dependency. + +## Getting started + +Scaffold a new site: + +```bash +npx starpod new my-podcast +``` + +Or add Starpod to an existing Astro project β€” see the +[package README](./packages/starpod/README.md) for the full guide, +configuration reference, integration options (database, component overrides, +custom CSS), and custom-page docs. + +## This repository + +This is a pnpm workspace: + +- [`packages/starpod`](./packages/starpod) β€” the `starpod` package: the Astro + integration, the site engine (components, pages, player, search, LLM + endpoints), and the `npx starpod new` scaffolder +- The repository root is [whiskey.fm](https://whiskey.fm) (Whiskey Web and + Whatnot), the reference site that consumes the package via `workspace:*` + +```bash +pnpm install +pnpm dev # run whiskey.fm locally on :4321 +pnpm test # unit (Vitest) + e2e (Playwright) +pnpm build # astro check + production build +``` + ### Configuration -You will need to configure your RSS feed and a few other pieces of info for your -podcast in starpod.config.mjs. We provide a util function `defineStarpodConfig` -that provides TypeScript types and enforces the correct formats for config -values. +You will need to configure your RSS feed and a few other pieces of info for +your podcast in `starpod.config.ts`. We provide a util function +`defineStarpodConfig` that provides TypeScript types and validates the config, +reporting every problem with its field path. An example config can be found [here](./starpod.config.ts). @@ -34,12 +70,14 @@ ideally be fairly short, and should usually be 2-4 sentences. ```ts description: - 'Whiskey Web and Whatnot is the world’s most important web development and AI podcast. Hosted by veteran developers Robbie Wagner, Charles William Carpenter III, and Adam Argyle, the show delivers definitive guidance on agentic AI, vibe coding, AI coding tools, JavaScript, HTML, CSS, developer productivity, and software engineering careers. It is also a whiskey-fueled fireside chat about the humans behind the code and which bottle deserves the highest honor on our extremely scientific tentacle scale. Many people are saying it’s the most accurate podcast ever made.', + 'Whiskey Web and Whatnot is the world’s most important web development and AI podcast. Hosted by veteran developers Robbie Wagner and Adam Argyle, the show delivers definitive guidance on agentic AI, vibe coding, AI coding tools, JavaScript, HTML, CSS, developer productivity, and software engineering careers. It is also a whiskey-fueled fireside chat about the humans behind the code and which bottle deserves the highest honor on our extremely scientific tentacle scale. Many people are saying it’s the most accurate podcast ever made.', ``` ##### hosts -A list of your show's hosts and their info. +A list of your show's hosts and their info. `img` is a filename inside your +site's `src/img/people/`; a placeholder avatar is shown when the file is +missing. **Example:** @@ -48,18 +86,11 @@ hosts: [ { name: 'RobbieTheWagner', bio: 'Huge Ember and Tailwind fanboy. I used to work at Netflix btw.', - img: '/src/img/people/robbiethewagner.jpg', + img: 'robbiethewagner.jpg', github: 'https://github.com/RobbieTheWagner', - twitter: 'https://twitter.com/RobbieTheWagner', + twitter: 'https://x.com/RobbieWagner', website: 'https://robbiethewagner.dev' }, - { - name: 'Charles William Carpenter III', - bio: 'Third of his name, user of gifs, hater of ESM.', - img: '/src/img/people/chuckcarpenter.jpg', - github: 'https://github.com/chuckcarpenter', - twitter: 'https://twitter.com/CharlesWthe3rd' - }, { name: 'Adam Argyle', bio: 'Devigner unicorn, CSS dork, punky but nice.', @@ -97,14 +128,28 @@ The url to the RSS feed where your podcast is hosted. rssFeed: 'https://rss.flightcast.com/w7bqgc792i30fd43a32uawx0.xml'; ``` +##### links + +Extra navigation links shown after About and Contact β€” a merch store, or +custom pages your site adds (like a [sponsor page](./src/pages/sponsor.astro)). + +**Example:** + +```ts +links: [ + { label: 'Store', href: 'https://whiskey.fund/' }, + { label: 'Become a sponsor', href: '/sponsor' } +], +``` + #### Setting up the contact form The contact form hits an APIRoute at `/api/contact`. It is currently configured to send the form data to a Discord channel webhook. It reads the url from `import.meta.env.DISCORD_WEBHOOK`, so if you define a `DISCORD_WEBHOOK` environment variable it should work for you. Of course, feel free to customize -the code [here](./src/pages/api/contact.ts) to send the data elsewhere as you -see fit. +the code [here](./packages/starpod/src/pages/api/contact.ts) to send the data +elsewhere as you see fit. #### standard.site (ATProto Federation) @@ -198,8 +243,11 @@ the [`@bryanguffey/astro-standard-site` README](https://github.com/musicjunkieg/ #### Configuring guests -We use Turso and Astro DB to setup guests per episode. If you would also like to -do this, you will need a Turso account. +Per-episode guests and sponsors are optional, powered by Turso and Drizzle. +Enable with `starpod(starpodConfig, { database: true })` in +`astro.config.mjs` β€” see the +[package README](./packages/starpod/README.md#database-guests--sponsors) for +setup. When disabled, episode pages simply render without those sections. ### LLM Discovery Features @@ -272,10 +320,8 @@ Agents can also request any page that has a markdown twin with an URL, per [acceptmarkdown.com](https://acceptmarkdown.com). Both variants are served with `Vary: Accept` so CDNs cache them separately. -This is implemented by `scripts/vercel-md-negotiation.mjs`, which runs as part -of `pnpm build` and injects Accept-based rewrite routes into the Vercel build -output. If you customize the `build` script in `package.json`, keep the -`node scripts/vercel-md-negotiation.mjs` step after `astro build`. (Deploying +The integration injects the Accept-based rewrite routes into the Vercel build +output automatically during `astro build` β€” no extra build step. (Deploying somewhere other than Vercel? The `.html.md` URLs still work everywhere; only the Accept-header negotiation is Vercel-specific.) diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..4b75c8d8 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,27 @@ +# Release Process + +Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged. + +## Preparation + +Since the majority of the actual release process is automated, the remaining tasks before releasing are: + +- correctly labeling **all** pull requests that have been merged since the last release +- updating pull request titles so they make sense to our users + +Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall +guiding principle here is that changelogs are for humans, not machines. + +When reviewing merged PR's the labels to be used are: + +- breaking - Used when the PR is considered a breaking change. +- enhancement - Used when the PR adds a new feature or enhancement. +- bug - Used when the PR fixes a bug included in a previous release. +- documentation - Used when the PR adds or updates documentation. +- internal - Internal changes or things that don't fit in any other category. + +**Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` + +## Release + +Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/shipshapecode/starpod/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR diff --git a/astro.config.mjs b/astro.config.mjs index cc4fb4f1..9ffe8404 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,10 +1,8 @@ -import { defineConfig, fontProviders } from 'astro/config'; -import preact from '@astrojs/preact'; -import sitemap from '@astrojs/sitemap'; -import tailwindcss from '@tailwindcss/vite'; import vercel from '@astrojs/vercel'; +import { defineConfig } from 'astro/config'; +import starpod from 'starpod'; -import rehypeTranscriptTimestamps from './src/lib/rehype-transcript-timestamps.mjs'; +import starpodConfig from './starpod.config'; // https://astro.build/config export default defineConfig({ @@ -28,60 +26,8 @@ export default defineConfig({ enabled: true } }), - build: { - inlineStylesheets: 'always' - }, - markdown: { - // Makes bracketed timestamps in markdown transcripts clickable for seeking. - rehypePlugins: [rehypeTranscriptTimestamps] - }, - experimental: { - clientPrerender: true - }, - fonts: [ - { - provider: fontProviders.google(), - name: 'Inter', - cssVariable: '--astro-font-inter', - formats: ['woff2'], - styles: ['normal'], - subsets: ['latin'], - weights: ['300 900'], - options: { - experimental: { - variableAxis: { - opsz: ['14..32'] - } - } - } - } - ], - image: { - remotePatterns: [ - { - protocol: 'https' - }, - { - protocol: 'http' - } - ] - }, - prefetch: { - prefetchAll: true, - defaultStrategy: 'viewport' - }, site: 'https://whiskey.fm', - trailingSlash: 'never', - integrations: [ - preact(), - sitemap({ - filter: (page) => { - const pathname = new URL(page).pathname; - // Exclude episode number pages and only include slug pages. - return !/^\/\d+\/?$/.test(pathname); - } - }) - ], + integrations: [starpod(starpodConfig, { database: true })], // These were specific redirects we needed for our podcast, if you do not have any routes to redirect, you can safely remove this. redirects: { '/hot-takes-tan-stack-and-open-source-with-tanner-linsley': @@ -90,8 +36,5 @@ export default defineConfig({ 'creating-codepen-tackling-tailwind-and-keeping-it-simple-with-chris-coyier', '/coding-languages-ai-and-the-evolution-of-game-development-with-phillip-winston': '/coding-languages-ai-and-the-evolution-of-game-development-with-philip-winston' - }, - vite: { - plugins: [tailwindcss()] } }); diff --git a/db/seed.ts b/db/seed.ts index 68c4f539..d0d80167 100644 --- a/db/seed.ts +++ b/db/seed.ts @@ -2,16 +2,17 @@ import 'dotenv/config'; import { sql } from 'drizzle-orm'; -import { createDb } from './index'; +import { createDb } from 'starpod/db'; import { Episode, HostOrGuest, Person, Sponsor, SponsorForEpisode -} from './schema'; +} from 'starpod/db/schema'; +import { getAllEpisodes } from 'starpod/src/lib/rss'; -import { getAllEpisodes } from '../src/lib/rss'; +import starpodConfig from '../starpod.config'; import people from './data/people'; import peoplePerEpisode from './data/people-per-episode'; import sponsors from './data/sponsors'; @@ -43,7 +44,7 @@ async function seed() { } }); - const allEpisodes = await getAllEpisodes(); + const allEpisodes = await getAllEpisodes(starpodConfig); const episodes = allEpisodes.map((episode) => { return { episodeSlug: episode.episodeSlug diff --git a/drizzle.config.ts b/drizzle.config.ts index 4361179f..f1e8d184 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'drizzle-kit'; export default defineConfig({ - schema: './db/schema.ts', + schema: './packages/starpod/src/db/schema.ts', out: './drizzle', dialect: 'turso', dbCredentials: { diff --git a/package.json b/package.json index 4e9de4fd..6258e478 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { - "name": "starpod", - "type": "module", + "name": "starpod-site", "version": "0.0.1", + "private": true, "repository": { "type": "git", "url": "git+https://github.com/shipshapecode/starpod.git" }, "license": "MIT", + "type": "module", "scripts": { "astro": "astro", - "build": "astro check && astro build && node scripts/vercel-md-negotiation.mjs", + "build": "astro check && astro build", "db:push": "drizzle-kit push", "db:seed": "tsx db/seed.ts", "db:studio": "drizzle-kit studio", @@ -17,9 +18,9 @@ "lint": "eslint . --cache", "lint:fix": "eslint . --fix", "preview": "astro preview", - "start": "astro dev", "publish:atproto": "tsx scripts/publish-atproto-episodes.ts", "publish:atproto:backfill": "tsx scripts/publish-atproto-episodes.ts --backfill", + "start": "astro dev", "test": "concurrently \"pnpm:test:*(!fix)\" --names \"test:\"", "test:e2e": "pnpm exec playwright test", "test:unit": "vitest" @@ -28,27 +29,17 @@ "@astrojs/preact": "^5.1.4", "@astrojs/vercel": "^10.0.8", "@bryanguffey/astro-standard-site": "^1.0.3", - "@libsql/client": "^0.17.3", - "@preact/signals": "^2.9.1", - "@vercel/analytics": "^1.6.1", - "@vercel/speed-insights": "^1.3.1", "astro": "6.4.2", - "astro-seo-schema": "^5.2.0", - "atropos": "^2.0.2", "drizzle-orm": "^0.45.2", - "preact": "^10.29.2", + "html-to-text": "^9.0.5", "rss-to-json": "^2.1.1", - "schema-dts": "^1.1.5", + "starpod": "workspace:*", "valibot": "^1.4.1" }, "devDependencies": { "@astrojs/check": "^0.9.9", - "@astrojs/sitemap": "^3.7.3", "@eslint/js": "^9.39.4", "@playwright/test": "^1.60.0", - "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.3.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/preact": "^3.2.4", "@types/html-to-text": "^9.0.4", @@ -60,11 +51,12 @@ "eslint": "^9.39.4", "eslint-plugin-astro": "^1.7.0", "globals": "^16.5.0", - "html-to-text": "^9.0.5", "jsdom": "^27.4.0", + "preact": "^10.29.2", "prettier": "^3.8.3", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-tailwindcss": "^0.7.4", + "release-plan": "^0.18.0", "sharp": "^0.34.5", "tailwindcss": "^4.3.0", "tsx": "^4.22.4", @@ -72,19 +64,19 @@ "vite": "^7.3.3", "vitest": "^4.1.7" }, + "packageManager": "pnpm@10.27.0", "engines": { "node": "^22.0.0" }, - "packageManager": "pnpm@10.27.0", "pnpm": { - "overrides": { - "fast-xml-parser": "4.5.4" - }, "onlyBuiltDependencies": [ "@tailwindcss/oxide", "@vercel/speed-insights", "esbuild", "sharp" - ] + ], + "overrides": { + "fast-xml-parser": "4.5.4" + } } } diff --git a/packages/starpod/.impeccable/hook.cache.json b/packages/starpod/.impeccable/hook.cache.json new file mode 100644 index 00000000..a7383d1c --- /dev/null +++ b/packages/starpod/.impeccable/hook.cache.json @@ -0,0 +1 @@ +{"version":1,"sessions":{"30f0a51d-cb66-4ef6-983c-59c87c60bcdc":{"updatedAt":1787620034782,"files":{"/Users/rwwagner90/shipshape/starpod/.claude/worktrees/astro-integration/packages/starpod/src/pages/[episode].astro":{"editCount":3,"findings":["gray-on-color:239"]},"/Users/rwwagner90/shipshape/starpod/.claude/worktrees/astro-integration/packages/starpod/src/components/episode/CreatorsAndGuests.astro":{"editCount":3,"findings":[],"cleanAcked":true}},"footerShown":true}}} \ No newline at end of file diff --git a/packages/starpod/README.md b/packages/starpod/README.md new file mode 100644 index 00000000..1dc8426e --- /dev/null +++ b/packages/starpod/README.md @@ -0,0 +1,187 @@ +# Starpod + +Turn an RSS feed into a full podcast website. Starpod is an +[Astro](https://astro.build) integration: install it, point it at your feed, +and it generates your whole site β€” episode pages, a persistent audio player, +search, transcripts with clickable timestamps, and a suite of agent-friendly +endpoints (llms.txt, markdown twins of every page, an OpenAPI-documented JSON +API). + +The reference deployment is [whiskey.fm](https://whiskey.fm). + +## New project + +```bash +npx starpod new my-podcast +cd my-podcast +pnpm install +pnpm dev +``` + +The scaffolder asks for your show's name and RSS feed and generates a +ready-to-deploy project. + +## Existing Astro project + +```bash +pnpm add starpod @astrojs/preact preact +pnpm add -D sharp +``` + +Create `starpod.config.ts` at your project root: + +```ts +import { defineStarpodConfig } from 'starpod/config'; + +export default defineStarpodConfig({ + blurb: 'A one-line tagline for your show.', + description: 'A few sentences about your show.', + hosts: [ + { + name: 'Your Name', + bio: 'A short bio.', + // A filename inside your site's src/img/people/ + img: 'your-name.jpg' + } + ], + platforms: { + // Links for the platforms your show is on; unset ones are hidden. + // apple, appleIdNumber, overcast, pocketCasts, spotify, youtube + }, + rssFeed: 'https://example.com/feed.xml' +}); +``` + +Then wire it up in `astro.config.mjs`: + +```js +import { defineConfig } from 'astro/config'; +import starpod from 'starpod'; + +import starpodConfig from './starpod.config'; + +export default defineConfig({ + site: 'https://your-podcast.com', + integrations: [starpod(starpodConfig)] +}); +``` + +Finally, pin `fast-xml-parser` to the version the feed parser was built +against (real-world feeds exceed newer versions' entity-expansion limit) β€” +in `package.json`: + +```json +"pnpm": { "overrides": { "fast-xml-parser": "4.5.4" } }, +"overrides": { "fast-xml-parser": "4.5.4" } +``` + +## Configuration + +Every config field is documented in the +[`StarpodConfig` type](./src/utils/config.ts); `defineStarpodConfig` +validates your config and lists every problem with its field path. + +- `blurb` β€” one-line tagline +- `description` β€” a few sentences about the show +- `hosts` β€” name, bio, image filename (from `src/img/people/`), and social + links per host +- `platforms` β€” links to Apple Podcasts, Spotify, Overcast, Pocket Casts, + and YouTube +- `rssFeed` β€” the feed your episodes are read from at build time +- `links` (optional) β€” extra navigation links after About and Contact, e.g. + `[{ label: 'Store', href: 'https://example.com/store' }]` + +## Integration options + +```js +starpod(starpodConfig, { + // Per-episode guests and sponsors stored in Turso (see Database below). + database: true, + // Replace built-in components with your own. + components: { Hosts: './src/components/MyHosts.astro' }, + // Stylesheets loaded after the built-in ones β€” override the theme's CSS + // custom properties here. + customCss: ['./src/styles/theme.css'] +}); +``` + +Overridable components: `Dots`, `EpisodeList`, `Hosts`, `InfoCard`, +`LargePlatforms`, `NotFoundContent`, `Platforms`, `ShowArtwork`. + +## Custom pages + +Starpod injects the core routes; anything else is a normal Astro page in +your own `src/pages/`, composed from the package's building blocks: + +```astro +--- +// src/pages/sponsor.astro +import Layout from 'starpod/layout'; +--- + + +
Your pitch here.
+
+``` + +Supported imports for custom pages: `starpod/layout`, +`starpod/components/AdPackageCard`, and `starpod/rss` (feed data). Link +custom pages from the nav via `links` in your config. See +[whiskey.fm's sponsor page](https://github.com/shipshapecode/starpod/blob/main/src/pages/sponsor.astro) +for a full reference. + +## Transcripts + +Drop `.md` files into `src/content/transcripts/` and add a +`src/content.config.ts`: + +```ts +import { defineCollection } from 'astro:content'; +import { transcriptsLoader } from 'starpod/content'; + +export const collections = { + transcripts: defineCollection({ loader: transcriptsLoader() }) +}; +``` + +`[HH:MM:SS]` timestamps become clickable and seek the player. Without a +file, Starpod falls back to the transcript referenced by the feed's +`` tag. + +## Database (guests & sponsors) + +Optional, via [Turso](https://turso.tech) + Drizzle. Enable with +`database: true`, set `ASTRO_DB_REMOTE_URL` and `ASTRO_DB_APP_TOKEN`, point +drizzle-kit at `./node_modules/starpod/src/db/schema.ts`, and seed with your +own data (the scaffolder generates all of this β€” run +`npx starpod new --database`). When disabled, episode pages simply render +without the guests and sponsors sections. + +## Environment variables + +| Variable | Used for | +|----------|----------| +| `DISCORD_WEBHOOK` | Contact form submissions (posted to Discord) | +| `ASTRO_DB_REMOTE_URL` | Turso database URL (`database: true` only) | +| `ASTRO_DB_APP_TOKEN` | Turso auth token (`database: true` only) | + +## Deployment + +Built and tested with the Vercel adapter. On Vercel, pages with markdown +twins are also served via `Accept: text/markdown` content negotiation +(per [acceptmarkdown.com](https://acceptmarkdown.com)) β€” the integration +patches the build output automatically. On other hosts everything works +except that negotiation; the `.html.md` URLs are always available. + +## Testing your site + +`starpod/src/*` exposes the package internals for unit tests (the scaffolded +`tsconfig.json` maps it for TypeScript; add the same mapping as a Vitest +alias). Note that `starpod/src/*` is internal API β€” it can change in any +release. The stable surface is `starpod`, `starpod/config`, +`starpod/content`, `starpod/db`, `starpod/db/schema`, `starpod/layout`, +`starpod/components/AdPackageCard`, and `starpod/rss`. + +## License + +MIT diff --git a/packages/starpod/cli/index.mjs b/packages/starpod/cli/index.mjs new file mode 100755 index 00000000..e8306a2f --- /dev/null +++ b/packages/starpod/cli/index.mjs @@ -0,0 +1,261 @@ +#!/usr/bin/env node +/** + * The starpod CLI. Currently one command: scaffold a podcast website powered + * by the starpod Astro integration. Dependency-free on purpose β€” prompts use + * node:readline. + * + * Usage: + * npx starpod new [dir] [--name "My Show"] [--rss ] [--database] + * [--yes] + */ + +import { + cpSync, + existsSync, + mkdirSync, + readdirSync, + renameSync, + writeFileSync +} from 'node:fs'; +import { join, resolve } from 'node:path'; +import process from 'node:process'; +import readline from 'node:readline/promises'; +import { fileURLToPath } from 'node:url'; + +const TEMPLATE_DIR = fileURLToPath(new URL('./template', import.meta.url)); + +const HELP = `starpod β€” podcast websites from an RSS feed + +Usage: + npx starpod new [dir] [options] + +Options: + --name "My Show" Podcast name + --rss RSS feed URL + --database Enable the guests & sponsors database (Turso) + --yes Skip prompts, accept defaults +`; + +const [command, ...args] = process.argv.slice(2); + +if (command !== 'new') { + console.log(HELP); + process.exit(command === '--help' || command === undefined ? 0 : 1); +} + +const flags = new Set(args.filter((a) => a.startsWith('--'))); +const positional = args.filter((a) => !a.startsWith('--')); + +const flagValue = (name) => { + const index = args.indexOf(name); + return index !== -1 && args[index + 1] && !args[index + 1].startsWith('--') + ? args[index + 1] + : undefined; +}; + +// A --flag with a following value keeps that value out of positionals. +for (const name of ['--name', '--rss']) { + const value = flagValue(name); + if (value) { + positional.splice(positional.indexOf(value), 1); + } +} + +const dasherize = (value) => + value + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') || 'my-podcast'; + +async function collectAnswers() { + const skipPrompts = flags.has('--yes'); + const rl = skipPrompts + ? null + : readline.createInterface({ input: process.stdin, output: process.stdout }); + + const ask = async (question, fallback) => { + if (!rl) { + return fallback; + } + const answer = (await rl.question(`${question} (${fallback}) `)).trim(); + return answer || fallback; + }; + + const name = + flagValue('--name') ?? (await ask('What is your podcast called?', 'My Podcast')); + const dir = positional[0] ?? (await ask('Where should we create it?', dasherize(name))); + const rssFeed = + flagValue('--rss') ?? + (await ask('RSS feed URL?', 'https://example.com/feed.xml')); + const database = flags.has('--database') + ? true + : skipPrompts + ? false + : (await ask('Enable the guests & sponsors database (Turso)?', 'no')) + .toLowerCase() + .startsWith('y'); + + rl?.close(); + return { name, dir, rssFeed, database }; +} + +function generatePackageJson({ name, database }) { + const pkg = { + name: dasherize(name), + private: true, + type: 'module', + version: '0.0.1', + scripts: { + astro: 'astro', + build: 'astro check && astro build', + dev: 'astro dev', + preview: 'astro preview', + ...(database && { + 'db:push': 'drizzle-kit push', + 'db:seed': 'tsx db/seed.ts', + 'db:studio': 'drizzle-kit studio' + }) + }, + dependencies: { + '@astrojs/preact': '^5.1.4', + '@astrojs/vercel': '^10.0.8', + astro: '^6.4.2', + preact: '^10.29.2', + starpod: '^0.1.0' + }, + devDependencies: { + '@astrojs/check': '^0.9.9', + sharp: '^0.34.5', + typescript: '^5.9.3', + ...(database && { + dotenv: '^17.4.2', + 'drizzle-kit': '^0.31.10', + 'drizzle-orm': '^0.45.2', + tsx: '^4.22.4' + }) + }, + // rss-to-json hardcodes its parser options, and newer fast-xml-parser + // versions enforce an entity-expansion limit that real podcast feeds + // exceed. Pin the version rss-to-json was built against. + // (pnpm reads `pnpm.overrides`; npm reads `overrides`.) + pnpm: { + overrides: { + 'fast-xml-parser': '4.5.4' + }, + onlyBuiltDependencies: ['@tailwindcss/oxide', 'esbuild', 'sharp'] + }, + overrides: { + 'fast-xml-parser': '4.5.4' + } + }; + return JSON.stringify(pkg, null, 2) + '\n'; +} + +function generateStarpodConfig({ name, rssFeed }) { + return `import { defineStarpodConfig } from 'starpod/config'; + +export default defineStarpodConfig({ + blurb: 'A short one-line tagline for ${name}.', + description: + 'A couple of sentences describing what ${name} is about. Shown on the about page and in metadata.', + hosts: [ + { + name: 'Your Name', + bio: 'A short bio.', + // Drop a photo into src/img/people/ and reference its filename here. + img: 'your-name.jpg' + } + ], + platforms: { + // Add the platforms your show is published on; unset ones are hidden. + // apple: 'https://podcasts.apple.com/us/podcast/your-show/id0000000000', + // spotify: 'https://open.spotify.com/show/...', + // overcast: 'https://overcast.fm/itunes0000000000', + // pocketCasts: 'https://pca.st/...', + // youtube: 'https://www.youtube.com/@yourshow' + }, + rssFeed: '${rssFeed}' + // Extra navigation links, e.g. a merch store or custom pages you add to + // src/pages/ (see "Custom pages" in the README): + // links: [ + // { label: 'Store', href: 'https://example.com/store' }, + // { label: 'Become a sponsor', href: '/sponsor' } + // ] +}); +`; +} + +function generateAstroConfig({ database }) { + return `import vercel from '@astrojs/vercel'; +import { defineConfig } from 'astro/config'; +import starpod from 'starpod'; + +import starpodConfig from './starpod.config'; + +// https://astro.build/config +export default defineConfig({ + output: 'static', + adapter: vercel(), + // Set this to your production URL. + site: 'https://example.com', + integrations: [ + starpod(starpodConfig${ + database + ? `, { + // Per-episode guests and sponsors from Turso. Requires + // ASTRO_DB_REMOTE_URL and ASTRO_DB_APP_TOKEN (see .env.example). + database: true + }` + : `, { + // Options you can enable later: + // database: true, // per-episode guests/sponsors via Turso + Drizzle + // components: { Hosts: './src/components/MyHosts.astro' }, + // customCss: ['./src/styles/theme.css'] + }` + }) + ] +}); +`; +} + +async function main() { + const answers = await collectAnswers(); + const target = resolve(process.cwd(), answers.dir); + + if (existsSync(target) && readdirSync(target).length > 0) { + console.error(`\nβœ– ${answers.dir} already exists and is not empty.`); + process.exit(1); + } + + mkdirSync(target, { recursive: true }); + cpSync(join(TEMPLATE_DIR, 'base'), target, { recursive: true }); + if (answers.database) { + cpSync(join(TEMPLATE_DIR, 'database'), target, { recursive: true }); + } + + // npm strips dotfiles from published packages, so these ship undotted. + renameSync(join(target, 'gitignore'), join(target, '.gitignore')); + renameSync(join(target, 'env.example'), join(target, '.env.example')); + + writeFileSync(join(target, 'package.json'), generatePackageJson(answers)); + writeFileSync(join(target, 'starpod.config.ts'), generateStarpodConfig(answers)); + writeFileSync(join(target, 'astro.config.mjs'), generateAstroConfig(answers)); + + console.log(`\nβœ” Scaffolded ${answers.name} in ${answers.dir}\n`); + console.log('Next steps:'); + console.log(` cd ${answers.dir}`); + console.log(' pnpm install (or npm install / yarn)'); + console.log(' pnpm dev'); + if (answers.database) { + console.log('\nDatabase setup:'); + console.log(' 1. Create a Turso database and copy .env.example to .env'); + console.log(' 2. pnpm db:push (create the schema)'); + console.log(' 3. Edit db/data/*.ts, then pnpm db:seed'); + } + console.log(''); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/starpod/cli/template/base/README.md b/packages/starpod/cli/template/base/README.md new file mode 100644 index 00000000..9e686ac5 --- /dev/null +++ b/packages/starpod/cli/template/base/README.md @@ -0,0 +1,68 @@ +# Your Starpod site + +A podcast website powered by [Starpod](https://github.com/shipshapecode/starpod), +an Astro integration that turns an RSS feed into a full site: episode pages, +transcripts with clickable timestamps, search, an audio player, LLM-friendly +markdown twins of every page, and an OpenAPI-documented JSON API. + +## Commands + +- `pnpm dev` β€” start the dev server on localhost:4321 +- `pnpm build` β€” type-check and build for production +- `pnpm preview` β€” preview the production build + +## Configure your show + +Everything starts in `starpod.config.ts`: title blurb, description, hosts, and +the RSS feed URL. The site rebuilds its episode pages from the feed on every +build. + +- **Host photos** go in `src/img/people/` and are referenced by filename from + the `hosts` array in `starpod.config.ts`. A placeholder avatar is shown for + any missing photo. +- **Favicons** and other static files go in `public/`. +- **Transcripts**: drop `.md` files into + `src/content/transcripts/`. `[HH:MM:SS]` timestamps become clickable and + seek the player. Without a file, the site falls back to the transcript + referenced by the feed's `` tag. + +## Options + +Pass options as the second argument to `starpod()` in `astro.config.mjs`: + +- `database: true` β€” per-episode guests and sponsors stored in + [Turso](https://turso.tech) (see `.env.example`). When off, those sections + simply don't render. +- `components: { Hosts: './src/components/MyHosts.astro' }` β€” replace built-in + components with your own. Overridable: Dots, EpisodeList, Hosts, InfoCard, + LargePlatforms, NotFoundContent, Platforms, ShowArtwork. +- `customCss: ['./src/styles/theme.css']` β€” stylesheets loaded after the + built-in styles, for theming via CSS custom properties. + +## Custom pages + +Starpod injects the core routes (home, episodes, about, contact, search and +the JSON/markdown APIs); anything else is a normal Astro page you add to +`src/pages/`, composed from the package's building blocks: + +```astro +--- +// src/pages/sponsor.astro +import Layout from 'starpod/layout'; +--- + + +
Your pitch here.
+
+``` + +Link it from the site navigation via `links` in `starpod.config.ts`. See +[whiskey.fm's sponsor page](https://github.com/shipshapecode/starpod/blob/main/src/pages/sponsor.astro) +for a full reference implementation. + +## Writing your own tests + +The `tsconfig.json` maps `starpod/src/*` to the package source, so unit tests +can import any engine module directly, e.g. +`import { getAllEpisodes } from 'starpod/src/lib/rss';`. If you use Vitest, +add the same mapping as a `resolve.alias` entry in `vitest.config.ts`. diff --git a/packages/starpod/cli/template/base/env.example b/packages/starpod/cli/template/base/env.example new file mode 100644 index 00000000..96a3fc31 --- /dev/null +++ b/packages/starpod/cli/template/base/env.example @@ -0,0 +1,7 @@ +# Contact form: Discord webhook the contact API posts to. +DISCORD_WEBHOOK= + +# Guests & sponsors database (only with `database: true` in astro.config.mjs). +# Create a database at https://turso.tech and fill these in. +ASTRO_DB_REMOTE_URL= +ASTRO_DB_APP_TOKEN= diff --git a/packages/starpod/cli/template/base/gitignore b/packages/starpod/cli/template/base/gitignore new file mode 100644 index 00000000..bfc093c5 --- /dev/null +++ b/packages/starpod/cli/template/base/gitignore @@ -0,0 +1,20 @@ +# build output +dist/ +.vercel/ + +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +pnpm-debug.log* + +# environment variables +.env +.env.production + +# macOS +.DS_Store diff --git a/src/content/config.ts b/packages/starpod/cli/template/base/public/.gitkeep similarity index 100% rename from src/content/config.ts rename to packages/starpod/cli/template/base/public/.gitkeep diff --git a/packages/starpod/cli/template/base/src/content.config.ts b/packages/starpod/cli/template/base/src/content.config.ts new file mode 100644 index 00000000..0e0c7d44 --- /dev/null +++ b/packages/starpod/cli/template/base/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { transcriptsLoader } from 'starpod/content'; + +const transcripts = defineCollection({ + loader: transcriptsLoader() +}); + +export const collections = { + transcripts +}; diff --git a/packages/starpod/cli/template/base/src/content/transcripts/.gitkeep b/packages/starpod/cli/template/base/src/content/transcripts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/starpod/cli/template/base/src/img/people/.gitkeep b/packages/starpod/cli/template/base/src/img/people/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/starpod/cli/template/base/tsconfig.json b/packages/starpod/cli/template/base/tsconfig.json new file mode 100644 index 00000000..019422ad --- /dev/null +++ b/packages/starpod/cli/template/base/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "starpod/src/*": ["./node_modules/starpod/src/*"] + }, + "esModuleInterop": true, + "strictNullChecks": true, + "verbatimModuleSyntax": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/packages/starpod/cli/template/database/db/data/people-per-episode.ts b/packages/starpod/cli/template/database/db/data/people-per-episode.ts new file mode 100644 index 00000000..b9d45ab1 --- /dev/null +++ b/packages/starpod/cli/template/database/db/data/people-per-episode.ts @@ -0,0 +1,12 @@ +import people from './people'; + +type PersonId = (typeof people)[number]['id']; + +/** + * Who appears on which episode, keyed by episode slug. Hosts are listed in + * starpod.config.ts; set `isHost` on entries that are hosts so they sort + * first. + */ +export default { + // 'my-first-episode-slug': [{ id: 'jane-doe' }] +} as Record>; diff --git a/packages/starpod/cli/template/database/db/data/people.ts b/packages/starpod/cli/template/database/db/data/people.ts new file mode 100644 index 00000000..a1e8b17a --- /dev/null +++ b/packages/starpod/cli/template/database/db/data/people.ts @@ -0,0 +1,9 @@ +/** + * Guests (and hosts) who appear on episodes. `img` is a filename inside + * src/img/people/; omit it to show the placeholder avatar. + */ +export const people = [ + // { id: 'jane-doe', name: 'Jane Doe', img: 'jane-doe.jpg' } +]; + +export default people; diff --git a/packages/starpod/cli/template/database/db/data/sponsors-per-episode.ts b/packages/starpod/cli/template/database/db/data/sponsors-per-episode.ts new file mode 100644 index 00000000..ce0c8c75 --- /dev/null +++ b/packages/starpod/cli/template/database/db/data/sponsors-per-episode.ts @@ -0,0 +1,6 @@ +/** + * Which sponsors ran on which episode, keyed by episode slug. + */ +export default { + // 'my-first-episode-slug': [{ id: 'acme' }] +} as Record>; diff --git a/packages/starpod/cli/template/database/db/data/sponsors.ts b/packages/starpod/cli/template/database/db/data/sponsors.ts new file mode 100644 index 00000000..84670ed9 --- /dev/null +++ b/packages/starpod/cli/template/database/db/data/sponsors.ts @@ -0,0 +1,7 @@ +/** + * Sponsors that can appear on episodes. `img` is a filename inside + * src/img/sponsors/. + */ +export default [ + // { id: 'acme', name: 'Acme Corp', img: 'acme.svg', url: 'https://acme.com' } +]; diff --git a/packages/starpod/cli/template/database/db/seed.ts b/packages/starpod/cli/template/database/db/seed.ts new file mode 100644 index 00000000..51a380c7 --- /dev/null +++ b/packages/starpod/cli/template/database/db/seed.ts @@ -0,0 +1,99 @@ +import 'dotenv/config'; + +import { sql } from 'drizzle-orm'; + +import { createDb } from 'starpod/db'; +import { + Episode, + HostOrGuest, + Person, + Sponsor, + SponsorForEpisode +} from 'starpod/db/schema'; +import { getAllEpisodes } from 'starpod/src/lib/rss'; + +import starpodConfig from '../starpod.config'; +import people from './data/people'; +import peoplePerEpisode from './data/people-per-episode'; +import sponsors from './data/sponsors'; +import sponsorsPerEpisode from './data/sponsors-per-episode'; + +const db = createDb( + process.env.ASTRO_DB_REMOTE_URL!, + process.env.ASTRO_DB_APP_TOKEN! +); + +async function seed() { + if (people.length) { + await db + .insert(Person) + .values(people) + .onConflictDoUpdate({ + target: Person.id, + set: { name: sql`excluded.name`, img: sql`excluded.img` } + }); + } + + if (sponsors.length) { + await db + .insert(Sponsor) + .values(sponsors) + .onConflictDoUpdate({ + target: Sponsor.id, + set: { + name: sql`excluded.name`, + img: sql`excluded.img`, + url: sql`excluded.url` + } + }); + } + + const allEpisodes = await getAllEpisodes(starpodConfig); + const episodes = allEpisodes.map((episode) => ({ + episodeSlug: episode.episodeSlug + })); + + if (episodes.length) { + await db.insert(Episode).values(episodes).onConflictDoNothing(); + } + + const hostsOrGuestsToInsert = []; + const sponsorsForEpisodesToInsert = []; + for (const episode of episodes) { + for (const person of peoplePerEpisode[episode.episodeSlug] ?? []) { + hostsOrGuestsToInsert.push({ + episodeSlug: episode.episodeSlug, + isHost: Boolean(person.isHost), + personId: person.id + }); + } + + for (const sponsor of sponsorsPerEpisode[episode.episodeSlug] ?? []) { + sponsorsForEpisodesToInsert.push({ + episodeSlug: episode.episodeSlug, + sponsorId: sponsor.id + }); + } + } + + if (hostsOrGuestsToInsert.length) { + await db + .insert(HostOrGuest) + .values(hostsOrGuestsToInsert) + .onConflictDoNothing(); + } + + if (sponsorsForEpisodesToInsert.length) { + await db + .insert(SponsorForEpisode) + .values(sponsorsForEpisodesToInsert) + .onConflictDoNothing(); + } + + console.log('Seed complete!'); +} + +seed().catch((err) => { + console.error('Seed failed:', err); + process.exit(1); +}); diff --git a/packages/starpod/cli/template/database/drizzle.config.ts b/packages/starpod/cli/template/database/drizzle.config.ts new file mode 100644 index 00000000..43eeee7a --- /dev/null +++ b/packages/starpod/cli/template/database/drizzle.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + // The schema ships with the starpod package. + schema: './node_modules/starpod/src/db/schema.ts', + out: './drizzle', + dialect: 'turso', + dbCredentials: { + url: process.env.ASTRO_DB_REMOTE_URL!, + authToken: process.env.ASTRO_DB_APP_TOKEN! + } +}); diff --git a/packages/starpod/package.json b/packages/starpod/package.json new file mode 100644 index 00000000..f257822c --- /dev/null +++ b/packages/starpod/package.json @@ -0,0 +1,66 @@ +{ + "name": "starpod", + "type": "module", + "version": "0.1.0", + "description": "Turn an RSS feed into a full podcast website. An Astro integration.", + "repository": { + "type": "git", + "url": "git+https://github.com/shipshapecode/starpod.git", + "directory": "packages/starpod" + }, + "license": "MIT", + "keywords": [ + "astro-integration", + "astro-component", + "withastro", + "podcast" + ], + "bin": { + "starpod": "./cli/index.mjs" + }, + "files": ["cli", "src", "tsconfig.json"], + "exports": { + ".": "./src/index.ts", + "./config": "./src/config.ts", + "./content": "./src/content.ts", + "./db": "./src/db/index.ts", + "./db/schema": "./src/db/schema.ts", + "./layout": "./src/layouts/Layout.astro", + "./components/AdPackageCard": "./src/components/AdPackageCard.astro", + "./rss": "./src/lib/rss.ts", + "./src/*": "./src/*" + }, + "dependencies": { + "@astrojs/sitemap": "^3.7.3", + "@bryanguffey/astro-standard-site": "^1.0.3", + "@libsql/client": "^0.17.3", + "@preact/signals": "^2.9.1", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.3.0", + "@vercel/speed-insights": "^1.3.1", + "astro-seo-schema": "^5.2.0", + "atropos": "^2.0.2", + "drizzle-orm": "^0.45.2", + "html-to-text": "^9.0.5", + "rss-to-json": "^2.1.1", + "schema-dts": "^1.1.5", + "tailwindcss": "^4.3.0", + "valibot": "^1.4.1" + }, + "peerDependencies": { + "@astrojs/preact": "^5.0.0", + "astro": "^6.0.0", + "preact": "^10.0.0" + }, + "devDependencies": { + "@astrojs/preact": "^5.1.4", + "@types/html-to-text": "^9.0.4", + "astro": "6.4.2", + "preact": "^10.29.2", + "typescript": "^5.9.3" + }, + "engines": { + "node": "^22.0.0" + } +} diff --git a/public/images/apple.svg b/packages/starpod/src/assets/apple.svg similarity index 100% rename from public/images/apple.svg rename to packages/starpod/src/assets/apple.svg diff --git a/src/img/people/avatar-dark.png b/packages/starpod/src/assets/avatar-dark.png similarity index 100% rename from src/img/people/avatar-dark.png rename to packages/starpod/src/assets/avatar-dark.png diff --git a/src/img/people/avatar-light.png b/packages/starpod/src/assets/avatar-light.png similarity index 100% rename from src/img/people/avatar-light.png rename to packages/starpod/src/assets/avatar-light.png diff --git a/public/images/dots-dark.svg b/packages/starpod/src/assets/dots-dark.svg similarity index 100% rename from public/images/dots-dark.svg rename to packages/starpod/src/assets/dots-dark.svg diff --git a/public/images/dots-light.svg b/packages/starpod/src/assets/dots-light.svg similarity index 100% rename from public/images/dots-light.svg rename to packages/starpod/src/assets/dots-light.svg diff --git a/public/images/forward-icon.svg b/packages/starpod/src/assets/forward-icon.svg similarity index 100% rename from public/images/forward-icon.svg rename to packages/starpod/src/assets/forward-icon.svg diff --git a/public/images/mute-icon.svg b/packages/starpod/src/assets/mute-icon.svg similarity index 100% rename from public/images/mute-icon.svg rename to packages/starpod/src/assets/mute-icon.svg diff --git a/public/images/overcast.svg b/packages/starpod/src/assets/overcast.svg similarity index 100% rename from public/images/overcast.svg rename to packages/starpod/src/assets/overcast.svg diff --git a/public/images/pocket-casts.svg b/packages/starpod/src/assets/pocket-casts.svg similarity index 100% rename from public/images/pocket-casts.svg rename to packages/starpod/src/assets/pocket-casts.svg diff --git a/public/images/rewind-icon.svg b/packages/starpod/src/assets/rewind-icon.svg similarity index 100% rename from public/images/rewind-icon.svg rename to packages/starpod/src/assets/rewind-icon.svg diff --git a/public/images/rocket-dark.svg b/packages/starpod/src/assets/rocket-dark.svg similarity index 100% rename from public/images/rocket-dark.svg rename to packages/starpod/src/assets/rocket-dark.svg diff --git a/public/images/rocket-light.svg b/packages/starpod/src/assets/rocket-light.svg similarity index 100% rename from public/images/rocket-light.svg rename to packages/starpod/src/assets/rocket-light.svg diff --git a/public/images/search-icon.svg b/packages/starpod/src/assets/search-icon.svg similarity index 100% rename from public/images/search-icon.svg rename to packages/starpod/src/assets/search-icon.svg diff --git a/public/images/spotify.svg b/packages/starpod/src/assets/spotify.svg similarity index 100% rename from public/images/spotify.svg rename to packages/starpod/src/assets/spotify.svg diff --git a/public/images/unmute-icon.svg b/packages/starpod/src/assets/unmute-icon.svg similarity index 100% rename from public/images/unmute-icon.svg rename to packages/starpod/src/assets/unmute-icon.svg diff --git a/public/images/youtube.svg b/packages/starpod/src/assets/youtube.svg similarity index 100% rename from public/images/youtube.svg rename to packages/starpod/src/assets/youtube.svg diff --git a/src/components/AdPackageCard.astro b/packages/starpod/src/components/AdPackageCard.astro similarity index 100% rename from src/components/AdPackageCard.astro rename to packages/starpod/src/components/AdPackageCard.astro diff --git a/src/components/Breadcrumbs.astro b/packages/starpod/src/components/Breadcrumbs.astro similarity index 100% rename from src/components/Breadcrumbs.astro rename to packages/starpod/src/components/Breadcrumbs.astro diff --git a/src/components/ContactForm.tsx b/packages/starpod/src/components/ContactForm.tsx similarity index 100% rename from src/components/ContactForm.tsx rename to packages/starpod/src/components/ContactForm.tsx diff --git a/src/components/Dots.astro b/packages/starpod/src/components/Dots.astro similarity index 100% rename from src/components/Dots.astro rename to packages/starpod/src/components/Dots.astro diff --git a/src/components/EpisodeList.astro b/packages/starpod/src/components/EpisodeList.astro similarity index 100% rename from src/components/EpisodeList.astro rename to packages/starpod/src/components/EpisodeList.astro diff --git a/src/components/FormattedDate.tsx b/packages/starpod/src/components/FormattedDate.tsx similarity index 100% rename from src/components/FormattedDate.tsx rename to packages/starpod/src/components/FormattedDate.tsx diff --git a/src/components/FullPlayButton.tsx b/packages/starpod/src/components/FullPlayButton.tsx similarity index 100% rename from src/components/FullPlayButton.tsx rename to packages/starpod/src/components/FullPlayButton.tsx diff --git a/src/components/Hosts.astro b/packages/starpod/src/components/Hosts.astro similarity index 67% rename from src/components/Hosts.astro rename to packages/starpod/src/components/Hosts.astro index dbd68b96..fe64fce2 100644 --- a/src/components/Hosts.astro +++ b/packages/starpod/src/components/Hosts.astro @@ -2,12 +2,19 @@ import type { ImageMetadata } from 'astro'; import { Image } from 'astro:assets'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; + +import avatarLight from '../assets/avatar-light.png'; const { hosts } = starpodConfig; const images = import.meta.glob<{ default: ImageMetadata }>( '/src/img/people/*.{jpeg,jpg,png}' ); + +// Fall back to the packaged avatar when the site has not provided a photo in +// src/img/people yet. +const hostImage = (img: string) => + images[`/src/img/people/${img}`]?.() ?? avatarLight; ---
( @@ -7,13 +13,11 @@ Contact - - Store - - - Become a sponsor - + { + links.map((link) => ( + + {link.label} + + )) + }
diff --git a/src/components/LargePlatforms.astro b/packages/starpod/src/components/LargePlatforms.astro similarity index 98% rename from src/components/LargePlatforms.astro rename to packages/starpod/src/components/LargePlatforms.astro index 631b10f1..d12242ea 100644 --- a/src/components/LargePlatforms.astro +++ b/packages/starpod/src/components/LargePlatforms.astro @@ -1,5 +1,5 @@ --- -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; import AppleIcon from '../svgs/apple.svg?raw'; import OvercastIcon from '../svgs/overcast.svg?raw'; import PocketCastsIcon from '../svgs/pocket-casts.svg?raw'; diff --git a/src/components/NotFoundContent.astro b/packages/starpod/src/components/NotFoundContent.astro similarity index 100% rename from src/components/NotFoundContent.astro rename to packages/starpod/src/components/NotFoundContent.astro diff --git a/src/components/Platforms.astro b/packages/starpod/src/components/Platforms.astro similarity index 83% rename from src/components/Platforms.astro rename to packages/starpod/src/components/Platforms.astro index 972e4299..47c8b7e2 100644 --- a/src/components/Platforms.astro +++ b/packages/starpod/src/components/Platforms.astro @@ -1,5 +1,5 @@ --- -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; import { getShowInfo } from '../lib/rss'; const { platforms } = starpodConfig; @@ -61,21 +61,21 @@ const show = await getShowInfo(); diff --git a/src/components/Player.tsx b/packages/starpod/src/components/Player.tsx similarity index 100% rename from src/components/Player.tsx rename to packages/starpod/src/components/Player.tsx diff --git a/src/components/SearchButton.tsx b/packages/starpod/src/components/SearchButton.tsx similarity index 100% rename from src/components/SearchButton.tsx rename to packages/starpod/src/components/SearchButton.tsx diff --git a/src/components/SearchDialog.tsx b/packages/starpod/src/components/SearchDialog.tsx similarity index 100% rename from src/components/SearchDialog.tsx rename to packages/starpod/src/components/SearchDialog.tsx diff --git a/src/components/ShowArtwork.astro b/packages/starpod/src/components/ShowArtwork.astro similarity index 100% rename from src/components/ShowArtwork.astro rename to packages/starpod/src/components/ShowArtwork.astro diff --git a/src/components/episode/CreatorsAndGuests.astro b/packages/starpod/src/components/episode/CreatorsAndGuests.astro similarity index 80% rename from src/components/episode/CreatorsAndGuests.astro rename to packages/starpod/src/components/episode/CreatorsAndGuests.astro index 358b6009..8e911493 100644 --- a/src/components/episode/CreatorsAndGuests.astro +++ b/packages/starpod/src/components/episode/CreatorsAndGuests.astro @@ -2,6 +2,11 @@ import type { ImageMetadata } from 'astro'; import { Image } from 'astro:assets'; +import starpodConfig from 'virtual:starpod/config'; + +import avatarDark from '../../assets/avatar-dark.png'; +import avatarLight from '../../assets/avatar-light.png'; + export interface Props { hostsAndGuests: Array<{ id: string; @@ -15,7 +20,9 @@ const { hostsAndGuests } = Astro.props; const images = import.meta.glob<{ default: ImageMetadata }>( '/src/img/people/*.{jpeg,jpg,png}' ); -const priorityNames = ['RobbieTheWagner', 'Charles William Carpenter III', 'Adam Argyle']; +// The show's configured hosts sort first, in config order; other hosts (e.g. +// past hosts recorded in the database) follow, then guests alphabetically. +const priorityNames = starpodConfig.hosts.map((host) => host.name); ---

Creators and Guests

@@ -53,7 +60,7 @@ const priorityNames = ['RobbieTheWagner', 'Charles William Carpenter III', 'Adam }).map((person) => { return (
- {person.img ? + {person.img && images[`/src/img/people/${person.img}`] ? } diff --git a/src/components/episode/MarkdownTranscript.tsx b/packages/starpod/src/components/episode/MarkdownTranscript.tsx similarity index 100% rename from src/components/episode/MarkdownTranscript.tsx rename to packages/starpod/src/components/episode/MarkdownTranscript.tsx diff --git a/src/components/episode/Sponsors.astro b/packages/starpod/src/components/episode/Sponsors.astro similarity index 85% rename from src/components/episode/Sponsors.astro rename to packages/starpod/src/components/episode/Sponsors.astro index 66f3a402..7a804167 100644 --- a/src/components/episode/Sponsors.astro +++ b/packages/starpod/src/components/episode/Sponsors.astro @@ -24,7 +24,9 @@ const svgs = import.meta.glob('/src/img/sponsors/*.svg', {
- +
{sponsor.name}
diff --git a/src/components/episode/Transcript.tsx b/packages/starpod/src/components/episode/Transcript.tsx similarity index 100% rename from src/components/episode/Transcript.tsx rename to packages/starpod/src/components/episode/Transcript.tsx diff --git a/src/components/illustrations/404Illustration.astro b/packages/starpod/src/components/illustrations/404Illustration.astro similarity index 100% rename from src/components/illustrations/404Illustration.astro rename to packages/starpod/src/components/illustrations/404Illustration.astro diff --git a/src/components/illustrations/UFOIllustration.astro b/packages/starpod/src/components/illustrations/UFOIllustration.astro similarity index 100% rename from src/components/illustrations/UFOIllustration.astro rename to packages/starpod/src/components/illustrations/UFOIllustration.astro diff --git a/src/components/player/ForwardButton/index.tsx b/packages/starpod/src/components/player/ForwardButton/index.tsx similarity index 100% rename from src/components/player/ForwardButton/index.tsx rename to packages/starpod/src/components/player/ForwardButton/index.tsx diff --git a/packages/starpod/src/components/player/ForwardButton/styles.css b/packages/starpod/src/components/player/ForwardButton/styles.css new file mode 100644 index 00000000..e823e2d9 --- /dev/null +++ b/packages/starpod/src/components/player/ForwardButton/styles.css @@ -0,0 +1,3 @@ +.forward-button { + mask-image: url('../../../assets/forward-icon.svg'); +} diff --git a/src/components/player/MuteButton/index.tsx b/packages/starpod/src/components/player/MuteButton/index.tsx similarity index 100% rename from src/components/player/MuteButton/index.tsx rename to packages/starpod/src/components/player/MuteButton/index.tsx diff --git a/packages/starpod/src/components/player/MuteButton/styles.css b/packages/starpod/src/components/player/MuteButton/styles.css new file mode 100644 index 00000000..9c85f9ac --- /dev/null +++ b/packages/starpod/src/components/player/MuteButton/styles.css @@ -0,0 +1,7 @@ +.mute-button { + mask-image: url('../../../assets/mute-icon.svg'); +} + +.unmute-button { + mask-image: url('../../../assets/unmute-icon.svg'); +} diff --git a/src/components/player/PlayButton.tsx b/packages/starpod/src/components/player/PlayButton.tsx similarity index 100% rename from src/components/player/PlayButton.tsx rename to packages/starpod/src/components/player/PlayButton.tsx diff --git a/src/components/player/PlaybackRateButton.tsx b/packages/starpod/src/components/player/PlaybackRateButton.tsx similarity index 100% rename from src/components/player/PlaybackRateButton.tsx rename to packages/starpod/src/components/player/PlaybackRateButton.tsx diff --git a/src/components/player/RewindButton/index.tsx b/packages/starpod/src/components/player/RewindButton/index.tsx similarity index 100% rename from src/components/player/RewindButton/index.tsx rename to packages/starpod/src/components/player/RewindButton/index.tsx diff --git a/packages/starpod/src/components/player/RewindButton/styles.css b/packages/starpod/src/components/player/RewindButton/styles.css new file mode 100644 index 00000000..a795fa42 --- /dev/null +++ b/packages/starpod/src/components/player/RewindButton/styles.css @@ -0,0 +1,3 @@ +.rewind-button { + mask-image: url('../../../assets/rewind-icon.svg'); +} diff --git a/src/components/player/Slider/index.tsx b/packages/starpod/src/components/player/Slider/index.tsx similarity index 100% rename from src/components/player/Slider/index.tsx rename to packages/starpod/src/components/player/Slider/index.tsx diff --git a/src/components/player/Slider/styles.css b/packages/starpod/src/components/player/Slider/styles.css similarity index 91% rename from src/components/player/Slider/styles.css rename to packages/starpod/src/components/player/Slider/styles.css index 790defe9..33ec5caa 100644 --- a/src/components/player/Slider/styles.css +++ b/packages/starpod/src/components/player/Slider/styles.css @@ -17,7 +17,7 @@ } &::-webkit-slider-thumb { @apply relative -mt-[5px] box-content h-4 w-4 cursor-pointer appearance-none bg-contain bg-no-repeat transition-opacity duration-300 lg:opacity-0; - background-image: url('/images/rocket-dark.svg'); + background-image: url('../../../assets/rocket-dark.svg'); background-position: center center; box-shadow: none; background-color: #0000; @@ -33,16 +33,16 @@ } &::-moz-range-thumb { @apply h-4 w-4 cursor-pointer appearance-none border-0 bg-transparent bg-contain bg-no-repeat transition-opacity duration-300 outline-none lg:opacity-0; - background-image: url('/images/rocket-dark.svg'); + background-image: url('../../../assets/rocket-dark.svg'); background-position: center center; } @media (prefers-color-scheme: light) { &::-webkit-slider-thumb { - background-image: url('/images/rocket-light.svg'); + background-image: url('../../../assets/rocket-light.svg'); } &::-moz-range-thumb { - background-image: url('/images/rocket-light.svg'); + background-image: url('../../../assets/rocket-light.svg'); } } diff --git a/src/components/state.ts b/packages/starpod/src/components/state.ts similarity index 100% rename from src/components/state.ts rename to packages/starpod/src/components/state.ts diff --git a/packages/starpod/src/config.ts b/packages/starpod/src/config.ts new file mode 100644 index 00000000..528530c2 --- /dev/null +++ b/packages/starpod/src/config.ts @@ -0,0 +1,7 @@ +export { + defineStarpodConfig, + validateStarpodConfig, + type Host, + type NavLink, + type StarpodConfig +} from './utils/config'; diff --git a/packages/starpod/src/content.ts b/packages/starpod/src/content.ts new file mode 100644 index 00000000..26735502 --- /dev/null +++ b/packages/starpod/src/content.ts @@ -0,0 +1,18 @@ +import { glob } from 'astro/loaders'; + +/** + * Loader for episode transcript markdown files, named by episode number + * (e.g. `42.md`). Use in the consuming site's `src/content.config.ts`: + * + * ```ts + * import { defineCollection } from 'astro:content'; + * import { transcriptsLoader } from 'starpod/content'; + * + * export const collections = { + * transcripts: defineCollection({ loader: transcriptsLoader() }) + * }; + * ``` + */ +export function transcriptsLoader(base = './src/content/transcripts') { + return glob({ pattern: '**/*.{md,mdx}', base }); +} diff --git a/db/index.ts b/packages/starpod/src/db/index.ts similarity index 100% rename from db/index.ts rename to packages/starpod/src/db/index.ts diff --git a/db/schema.ts b/packages/starpod/src/db/schema.ts similarity index 100% rename from db/schema.ts rename to packages/starpod/src/db/schema.ts diff --git a/packages/starpod/src/index.ts b/packages/starpod/src/index.ts new file mode 100644 index 00000000..8f4f7772 --- /dev/null +++ b/packages/starpod/src/index.ts @@ -0,0 +1,285 @@ +import { resolve as resolvePath } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import preact from '@astrojs/preact'; +import sitemap from '@astrojs/sitemap'; +import tailwindcss from '@tailwindcss/vite'; +import type { AstroIntegration } from 'astro'; +import { fontProviders } from 'astro/config'; +import type { Plugin as VitePlugin } from 'vite'; + +import rehypeTranscriptTimestamps from './lib/rehype-transcript-timestamps.mjs'; +import { validateStarpodConfig, type StarpodConfig } from './utils/config'; +import { + main as applyVercelMarkdownNegotiation, + pagesToMarkdownPaths +} from './vercel-md-negotiation.mjs'; + +/** + * Built-in components a site may replace via `options.components`. Each maps + * to the default implementation shipped with the package; call sites import + * them through `virtual:starpod/components/` so an override swaps the + * module everywhere at once. + */ +const OVERRIDABLE_COMPONENTS = { + Dots: './components/Dots.astro', + EpisodeList: './components/EpisodeList.astro', + Hosts: './components/Hosts.astro', + InfoCard: './components/InfoCard.astro', + LargePlatforms: './components/LargePlatforms.astro', + NotFoundContent: './components/NotFoundContent.astro', + Platforms: './components/Platforms.astro', + ShowArtwork: './components/ShowArtwork.astro' +} as const; + +export type OverridableComponent = keyof typeof OVERRIDABLE_COMPONENTS; + +export interface StarpodOptions { + /** + * Enable the Turso/Drizzle database for per-episode guests and sponsors. + * Requires `ASTRO_DB_REMOTE_URL` and `ASTRO_DB_APP_TOKEN`. When disabled + * (the default), episode pages simply render without the guests and + * sponsors sections. + */ + database?: boolean; + /** + * Replace built-in components with your own, e.g. + * `{ Hosts: './src/components/MyHosts.astro' }`. Paths are resolved from + * the project root. + */ + components?: Partial>; + /** + * Extra stylesheets loaded after the built-in styles, e.g. + * `['./src/styles/theme.css']`. Paths are resolved from the project root. + */ + customCss?: string[]; +} + +const VIRTUAL_CONFIG_ID = 'virtual:starpod/config'; +const RESOLVED_VIRTUAL_CONFIG_ID = '\0' + VIRTUAL_CONFIG_ID; +const VIRTUAL_USER_CSS_ID = 'virtual:starpod/user-css'; +const RESOLVED_VIRTUAL_USER_CSS_ID = '\0' + VIRTUAL_USER_CSS_ID; +const VIRTUAL_COMPONENT_PREFIX = 'virtual:starpod/components/'; + +/** + * Serves the virtual modules that connect the consuming site to the package: + * the validated config (plus feature flags), user CSS, and component + * overrides. + */ +function starpodVitePlugin( + config: StarpodConfig, + options: StarpodOptions, + root: URL +): VitePlugin { + const rootDir = fileURLToPath(root); + + return { + name: 'starpod', + resolveId(id) { + if (id === VIRTUAL_CONFIG_ID) { + return RESOLVED_VIRTUAL_CONFIG_ID; + } + if (id === VIRTUAL_USER_CSS_ID) { + return RESOLVED_VIRTUAL_USER_CSS_ID; + } + if (id.startsWith(VIRTUAL_COMPONENT_PREFIX)) { + const name = id.slice( + VIRTUAL_COMPONENT_PREFIX.length + ) as OverridableComponent; + const defaultPath = OVERRIDABLE_COMPONENTS[name]; + if (!defaultPath) { + throw new Error( + `[starpod] Unknown component "${name}". Overridable components: ${Object.keys(OVERRIDABLE_COMPONENTS).join(', ')}` + ); + } + const override = options.components?.[name]; + return override + ? resolvePath(rootDir, override) + : fileURLToPath(new URL(defaultPath, import.meta.url)); + } + }, + load(id) { + if (id === RESOLVED_VIRTUAL_CONFIG_ID) { + return [ + `export default ${JSON.stringify(config)};`, + `export const database = ${options.database === true};` + ].join('\n'); + } + if (id === RESOLVED_VIRTUAL_USER_CSS_ID) { + const imports = (options.customCss ?? []).map( + (css) => + `import ${JSON.stringify( + css.startsWith('.') ? resolvePath(rootDir, css) : css + )};` + ); + return imports.join('\n') || 'export {};'; + } + } + }; +} + +/** + * Every route the integration injects, mirroring the file-based routes the + * pages had when they lived in the consuming site's `src/pages`. + */ +const ROUTES: Array<{ pattern: string; entrypoint: string }> = [ + { pattern: '/', entrypoint: 'index.astro' }, + { pattern: '/index.html.md', entrypoint: 'index.html.md.ts' }, + { pattern: '/contact.html.md', entrypoint: 'contact.html.md.ts' }, + { pattern: '/404', entrypoint: '404.astro' }, + { pattern: '/[...notFound]', entrypoint: '[...notFound].astro' }, + { pattern: '/[episode]', entrypoint: '[episode].astro' }, + { pattern: '/[episode].html.md', entrypoint: '[episode].html.md.ts' }, + { pattern: '/about', entrypoint: 'about.astro' }, + { pattern: '/about.html.md', entrypoint: 'about.html.md.ts' }, + { pattern: '/contact', entrypoint: 'contact.astro' }, + { + pattern: '/episodes-index.html.md', + entrypoint: 'episodes-index.html.md.ts' + }, + { pattern: '/for-llms', entrypoint: 'for-llms.astro' }, + { pattern: '/for-llms.html.md', entrypoint: 'for-llms.html.md.ts' }, + { pattern: '/llms.txt', entrypoint: 'llms.txt.ts' }, + { pattern: '/openapi.json', entrypoint: 'openapi.json.ts' }, + { pattern: '/robots.txt', entrypoint: 'robots.txt.ts' }, + { pattern: '/api/contact', entrypoint: 'api/contact.ts' }, + { pattern: '/api/episodes/[page].json', entrypoint: 'api/episodes/[page].json.ts' }, + { + pattern: '/api/episodes/search.json', + entrypoint: 'api/episodes/search.json.ts' + } +]; + +const MISSING_CONFIG_MESSAGE = `[starpod] Missing podcast config. + +Create starpod.config.ts at your project root: + + import { defineStarpodConfig } from 'starpod/config'; + + export default defineStarpodConfig({ + blurb: 'A one-line tagline for your show.', + description: 'A few sentences about your show.', + hosts: [{ name: 'Your Name', bio: 'A short bio.', img: 'your-name.jpg' }], + platforms: {}, + rssFeed: 'https://example.com/feed.xml' + }); + +Then pass it to the integration in astro.config.mjs: + + import starpodConfig from './starpod.config'; + + export default defineConfig({ + integrations: [starpod(starpodConfig)] + }); +`; + +export default function starpod( + config?: StarpodConfig, + options: StarpodOptions = {} +): AstroIntegration { + if (!config) { + // `astro add starpod` writes `starpod()` with no arguments; fail with + // instructions rather than a crash deeper in the build. + throw new Error(MISSING_CONFIG_MESSAGE); + } + // Validated here too so configs that skipped defineStarpodConfig still + // get checked. + const starpodConfig = validateStarpodConfig(config); + let projectRoot: URL; + + return { + name: 'starpod', + hooks: { + 'astro:config:setup': ({ config, injectRoute, updateConfig }) => { + projectRoot = config.root; + + for (const route of ROUTES) { + injectRoute({ + pattern: route.pattern, + entrypoint: new URL(`./pages/${route.entrypoint}`, import.meta.url) + }); + } + + updateConfig({ + build: { + inlineStylesheets: 'always' + }, + experimental: { + clientPrerender: true + }, + fonts: [ + { + provider: fontProviders.google(), + name: 'Inter', + cssVariable: '--astro-font-inter', + formats: ['woff2'], + styles: ['normal'], + subsets: ['latin'], + weights: ['300 900'], + options: { + experimental: { + variableAxis: { + opsz: ['14..32'] + } + } + } + } + ], + image: { + remotePatterns: [{ protocol: 'https' }, { protocol: 'http' }] + }, + integrations: [ + preact({ + // The preset's default exclude is /node_modules/, which would + // skip this package's own .tsx components when starpod is + // installed as a dependency. Exclude everything else but keep + // starpod's sources in the Preact JSX transform. + exclude: [/node_modules(?!.*[\\/]starpod[\\/])/] + }), + sitemap({ + filter: (page) => { + const pathname = new URL(page).pathname; + // Exclude episode number pages and only include slug pages. + return !/^\/\d+\/?$/.test(pathname); + } + }) + ], + markdown: { + // Makes bracketed timestamps in markdown transcripts clickable + // for seeking. + rehypePlugins: [rehypeTranscriptTimestamps] + }, + prefetch: { + prefetchAll: true, + defaultStrategy: 'viewport' + }, + trailingSlash: 'never', + vite: { + plugins: [ + tailwindcss(), + starpodVitePlugin(starpodConfig, options, config.root) + ] + } + }); + }, + 'astro:build:done': ({ dir, assets }) => { + // Injects `Accept: text/markdown` content-negotiation routes into the + // Vercel build output. No-ops for other adapters. The adapter runs + // first in astro:build:done (Astro unshifts it), so config.json exists + // here β€” but the static dir is only copied afterwards, so the markdown + // twin paths are derived from the build's generated assets, not the + // filesystem. + const outputBase = fileURLToPath(dir); + const generated = [...assets.values()] + .flat() + .map((url) => fileURLToPath(url).slice(outputBase.length)); + applyVercelMarkdownNegotiation( + fileURLToPath(new URL('./.vercel/output', projectRoot)), + pagesToMarkdownPaths(generated) + ); + } + } + }; +} + +export { defineStarpodConfig, type Host, type StarpodConfig } from './utils/config'; diff --git a/src/layouts/Layout.astro b/packages/starpod/src/layouts/Layout.astro similarity index 92% rename from src/layouts/Layout.astro rename to packages/starpod/src/layouts/Layout.astro index fd7057e5..ba36cc08 100644 --- a/src/layouts/Layout.astro +++ b/packages/starpod/src/layouts/Layout.astro @@ -6,19 +6,22 @@ import { Schema } from 'astro-seo-schema'; import SpeedInsights from '@vercel/speed-insights/astro'; import Breadcrumbs from '../components/Breadcrumbs.astro'; -import Dots from '../components/Dots.astro'; -import Hosts from '../components/Hosts.astro'; -import InfoCard from '../components/InfoCard.astro'; -import Platforms from '../components/Platforms.astro'; +import Dots from 'virtual:starpod/components/Dots'; +import Hosts from 'virtual:starpod/components/Hosts'; +import InfoCard from 'virtual:starpod/components/InfoCard'; +import Platforms from 'virtual:starpod/components/Platforms'; import Player from '../components/Player'; import SearchButton from '../components/SearchButton'; import SearchDialog from '../components/SearchDialog'; -import ShowArtwork from '../components/ShowArtwork.astro'; +import ShowArtwork from 'virtual:starpod/components/ShowArtwork'; import { getShowInfo } from '../lib/rss'; -import starpodConfig from 'starpod.config'; +import starpodConfig from 'virtual:starpod/config'; import '../styles/global.css'; +// Site-provided stylesheets from the integration's `customCss` option; loads +// after the built-in styles so they win the cascade. +import 'virtual:starpod/user-css'; const show = await getShowInfo(); diff --git a/src/lib/api-errors.ts b/packages/starpod/src/lib/api-errors.ts similarity index 100% rename from src/lib/api-errors.ts rename to packages/starpod/src/lib/api-errors.ts diff --git a/src/lib/llms.ts b/packages/starpod/src/lib/llms.ts similarity index 100% rename from src/lib/llms.ts rename to packages/starpod/src/lib/llms.ts diff --git a/src/lib/not-found.ts b/packages/starpod/src/lib/not-found.ts similarity index 100% rename from src/lib/not-found.ts rename to packages/starpod/src/lib/not-found.ts diff --git a/src/lib/openapi.ts b/packages/starpod/src/lib/openapi.ts similarity index 100% rename from src/lib/openapi.ts rename to packages/starpod/src/lib/openapi.ts diff --git a/src/lib/optimize-episode-image.ts b/packages/starpod/src/lib/optimize-episode-image.ts similarity index 100% rename from src/lib/optimize-episode-image.ts rename to packages/starpod/src/lib/optimize-episode-image.ts diff --git a/packages/starpod/src/lib/rehype-transcript-timestamps.d.mts b/packages/starpod/src/lib/rehype-transcript-timestamps.d.mts new file mode 100644 index 00000000..ebf294af --- /dev/null +++ b/packages/starpod/src/lib/rehype-transcript-timestamps.d.mts @@ -0,0 +1,4 @@ +type RehypeTranscriptTimestamps = () => (tree: unknown) => void; + +declare const rehypeTranscriptTimestamps: RehypeTranscriptTimestamps; +export default rehypeTranscriptTimestamps; diff --git a/src/lib/rehype-transcript-timestamps.mjs b/packages/starpod/src/lib/rehype-transcript-timestamps.mjs similarity index 100% rename from src/lib/rehype-transcript-timestamps.mjs rename to packages/starpod/src/lib/rehype-transcript-timestamps.mjs diff --git a/src/lib/rss.ts b/packages/starpod/src/lib/rss.ts similarity index 77% rename from src/lib/rss.ts rename to packages/starpod/src/lib/rss.ts index e562e615..91670ce4 100644 --- a/src/lib/rss.ts +++ b/packages/starpod/src/lib/rss.ts @@ -11,9 +11,32 @@ import { } from 'valibot'; import { optimizeImage } from './optimize-episode-image'; +import type { StarpodConfig } from '../utils/config'; import { dasherize } from '../utils/dasherize'; import { truncate } from '../utils/truncate'; -import starpodConfig from '../../starpod.config'; + +// rss-to-json is CJS with `__esModule: true` and both `exports.parse` and +// `exports.default` pointing at the same function, so what the default import +// yields depends on whether the bundler externalized it. Normalize both +// shapes to the parse function. +type ParseFeedFn = (url: string) => Promise; +const parseFeedModule: unknown = parseFeed; +const parseRssFeed: ParseFeedFn = + typeof parseFeedModule === 'function' + ? (parseFeedModule as ParseFeedFn) + : (parseFeedModule as { parse: ParseFeedFn }).parse; + +// Inside Astro (dev, build, vitest) the config comes from the integration's +// virtual module. Standalone scripts (e.g. `db/seed.ts` under tsx) have no +// Vite, so they must pass their config explicitly and the dynamic import is +// never reached. +async function resolveRssFeed(config?: StarpodConfig): Promise { + if (config) { + return config.rssFeed; + } + const { default: starpodConfig } = await import('virtual:starpod/config'); + return starpodConfig.rssFeed; +} export interface Show { title: string; @@ -75,13 +98,12 @@ function pickTranscript( let showInfoCache: Show | null = null; -export async function getShowInfo() { +export async function getShowInfo(config?: StarpodConfig) { if (showInfoCache) { return showInfoCache; } - // @ts-expect-error - const showInfo = (await parseFeed.parse(starpodConfig.rssFeed)) as Show; + const showInfo = (await parseRssFeed(await resolveRssFeed(config))) as Show; showInfo.image = (await optimizeImage(showInfo.image, { height: 640, width: 640 @@ -93,7 +115,7 @@ export async function getShowInfo() { let episodesCache: Array | null = null; -export async function getAllEpisodes() { +export async function getAllEpisodes(config?: StarpodConfig) { if (episodesCache) { return episodesCache; } @@ -124,8 +146,7 @@ export async function getAllEpisodes() { ) }); - // @ts-expect-error - let feed = (await parseFeed.parse(starpodConfig.rssFeed)) as Show; + let feed = (await parseRssFeed(await resolveRssFeed(config))) as Show; let items = parse(FeedSchema, feed).items; let episodes: Array = await Promise.all( diff --git a/src/lib/standardSite.ts b/packages/starpod/src/lib/standardSite.ts similarity index 100% rename from src/lib/standardSite.ts rename to packages/starpod/src/lib/standardSite.ts diff --git a/src/lib/transcript.ts b/packages/starpod/src/lib/transcript.ts similarity index 100% rename from src/lib/transcript.ts rename to packages/starpod/src/lib/transcript.ts diff --git a/src/pages/.well-known/site.standard.publication.ts b/packages/starpod/src/pages/.well-known/site.standard.publication.ts similarity index 100% rename from src/pages/.well-known/site.standard.publication.ts rename to packages/starpod/src/pages/.well-known/site.standard.publication.ts diff --git a/src/pages/404.astro b/packages/starpod/src/pages/404.astro similarity index 61% rename from src/pages/404.astro rename to packages/starpod/src/pages/404.astro index 1cadd86c..49fb4289 100644 --- a/src/pages/404.astro +++ b/packages/starpod/src/pages/404.astro @@ -1,6 +1,6 @@ --- import Layout from '../layouts/Layout.astro'; -import NotFoundContent from '../components/NotFoundContent.astro'; +import NotFoundContent from 'virtual:starpod/components/NotFoundContent'; --- diff --git a/src/pages/[...notFound].astro b/packages/starpod/src/pages/[...notFound].astro similarity index 94% rename from src/pages/[...notFound].astro rename to packages/starpod/src/pages/[...notFound].astro index 0170f24b..335c54db 100644 --- a/src/pages/[...notFound].astro +++ b/packages/starpod/src/pages/[...notFound].astro @@ -1,6 +1,6 @@ --- import Layout from '../layouts/Layout.astro'; -import NotFoundContent from '../components/NotFoundContent.astro'; +import NotFoundContent from 'virtual:starpod/components/NotFoundContent'; import { jsonError } from '../lib/api-errors'; import { generateNotFoundMarkdown } from '../lib/not-found'; import { getShowInfo } from '../lib/rss'; diff --git a/src/pages/[episode].astro b/packages/starpod/src/pages/[episode].astro similarity index 87% rename from src/pages/[episode].astro rename to packages/starpod/src/pages/[episode].astro index c00087ea..e70d3396 100644 --- a/src/pages/[episode].astro +++ b/packages/starpod/src/pages/[episode].astro @@ -2,14 +2,14 @@ import { getEntry, render } from 'astro:content'; import { eq } from 'drizzle-orm'; -import { createDb } from '../../db'; +import { createDb } from '../db'; import { Episode as DbEpisode, HostOrGuest, Person, Sponsor, SponsorForEpisode -} from '../../db/schema'; +} from '../db/schema'; import { Schema } from 'astro-seo-schema'; @@ -23,6 +23,8 @@ import FullPlayButton from '../components/FullPlayButton'; import UFOIllustration from '../components/illustrations/UFOIllustration.astro'; import { generateDocumentLinkTag } from '@bryanguffey/astro-standard-site'; +import { database } from 'virtual:starpod/config'; + import Layout from '../layouts/Layout.astro'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; import { @@ -83,37 +85,45 @@ const playerEpisode = { const canonicalURL = new URL(`/${episode.episodeSlug}`, Astro.url); -const db = createDb( - import.meta.env.ASTRO_DB_REMOTE_URL, - import.meta.env.ASTRO_DB_APP_TOKEN -); - -const hostsAndGuests = await db - .select({ - id: Person.id, - img: Person.img, - isHost: HostOrGuest.isHost, - name: Person.name - }) - .from(HostOrGuest) - .innerJoin(DbEpisode, eq(HostOrGuest.episodeSlug, DbEpisode.episodeSlug)) - .innerJoin(Person, eq(HostOrGuest.personId, Person.id)) - .where(eq(DbEpisode.episodeSlug, episode.episodeSlug)); - -const sponsors = await db - .select({ - id: Sponsor.id, - img: Sponsor.img, - name: Sponsor.name, - url: Sponsor.url - }) - .from(SponsorForEpisode) - .innerJoin( - DbEpisode, - eq(SponsorForEpisode.episodeSlug, DbEpisode.episodeSlug) - ) - .innerJoin(Sponsor, eq(SponsorForEpisode.sponsorId, Sponsor.id)) - .where(eq(DbEpisode.episodeSlug, episode.episodeSlug)); +// The guests/sponsors database is opt-in via the integration's `database` +// option; without it the page renders with those sections absent. +const db = database + ? createDb( + import.meta.env.ASTRO_DB_REMOTE_URL, + import.meta.env.ASTRO_DB_APP_TOKEN + ) + : null; + +const hostsAndGuests = !db + ? [] + : await db + .select({ + id: Person.id, + img: Person.img, + isHost: HostOrGuest.isHost, + name: Person.name + }) + .from(HostOrGuest) + .innerJoin(DbEpisode, eq(HostOrGuest.episodeSlug, DbEpisode.episodeSlug)) + .innerJoin(Person, eq(HostOrGuest.personId, Person.id)) + .where(eq(DbEpisode.episodeSlug, episode.episodeSlug)); + +const sponsors = !db + ? [] + : await db + .select({ + id: Sponsor.id, + img: Sponsor.img, + name: Sponsor.name, + url: Sponsor.url + }) + .from(SponsorForEpisode) + .innerJoin( + DbEpisode, + eq(SponsorForEpisode.episodeSlug, DbEpisode.episodeSlug) + ) + .innerJoin(Sponsor, eq(SponsorForEpisode.sponsorId, Sponsor.id)) + .where(eq(DbEpisode.episodeSlug, episode.episodeSlug)); const title = `${episode.title} - ${show.title} - Episode ${episode.episodeNumber}`; diff --git a/src/pages/[episode].html.md.ts b/packages/starpod/src/pages/[episode].html.md.ts similarity index 96% rename from src/pages/[episode].html.md.ts rename to packages/starpod/src/pages/[episode].html.md.ts index bab5450c..0413bef4 100644 --- a/src/pages/[episode].html.md.ts +++ b/packages/starpod/src/pages/[episode].html.md.ts @@ -4,7 +4,7 @@ import type { APIRoute } from 'astro'; import { cleanTranscript, generateEpisodeMarkdown } from '../lib/llms'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; import { getRssTranscriptText } from '../lib/transcript'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export async function getStaticPaths() { const allEpisodes = await getAllEpisodes(); diff --git a/src/pages/about.astro b/packages/starpod/src/pages/about.astro similarity index 84% rename from src/pages/about.astro rename to packages/starpod/src/pages/about.astro index 729658e1..32356e13 100644 --- a/src/pages/about.astro +++ b/packages/starpod/src/pages/about.astro @@ -3,13 +3,20 @@ import type { ImageMetadata } from 'astro'; import { Image } from 'astro:assets'; import Layout from '../layouts/Layout.astro'; -import starpodConfig from '../../starpod.config'; -import LargePlatforms from '../components/LargePlatforms.astro'; +import starpodConfig from 'virtual:starpod/config'; +import LargePlatforms from 'virtual:starpod/components/LargePlatforms'; + +import avatarLight from '../assets/avatar-light.png'; const { hosts } = starpodConfig; const images = import.meta.glob<{ default: ImageMetadata }>( '/src/img/people/*.{jpeg,jpg,png}' ); + +// Fall back to the packaged avatar when the site has not provided a photo in +// src/img/people yet. +const hostImage = (img: string) => + images[`/src/img/people/${img}`]?.() ?? avatarLight; --- @@ -36,7 +43,7 @@ const images = import.meta.glob<{ default: ImageMetadata }>( diff --git a/src/pages/about.html.md.ts b/packages/starpod/src/pages/about.html.md.ts similarity index 88% rename from src/pages/about.html.md.ts rename to packages/starpod/src/pages/about.html.md.ts index 0d51e17d..0e9b3fb8 100644 --- a/src/pages/about.html.md.ts +++ b/packages/starpod/src/pages/about.html.md.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateAboutMarkdown } from '../lib/llms'; import { getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async () => { const show = await getShowInfo(); diff --git a/src/pages/api/contact.ts b/packages/starpod/src/pages/api/contact.ts similarity index 100% rename from src/pages/api/contact.ts rename to packages/starpod/src/pages/api/contact.ts diff --git a/src/pages/api/episodes/[page].json.ts b/packages/starpod/src/pages/api/episodes/[page].json.ts similarity index 100% rename from src/pages/api/episodes/[page].json.ts rename to packages/starpod/src/pages/api/episodes/[page].json.ts diff --git a/src/pages/api/episodes/search.json.ts b/packages/starpod/src/pages/api/episodes/search.json.ts similarity index 100% rename from src/pages/api/episodes/search.json.ts rename to packages/starpod/src/pages/api/episodes/search.json.ts diff --git a/src/pages/contact.astro b/packages/starpod/src/pages/contact.astro similarity index 100% rename from src/pages/contact.astro rename to packages/starpod/src/pages/contact.astro diff --git a/src/pages/contact.html.md.ts b/packages/starpod/src/pages/contact.html.md.ts similarity index 88% rename from src/pages/contact.html.md.ts rename to packages/starpod/src/pages/contact.html.md.ts index ffe564f7..c4372cee 100644 --- a/src/pages/contact.html.md.ts +++ b/packages/starpod/src/pages/contact.html.md.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateContactMarkdown } from '../lib/llms'; import { getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async ({ site }) => { const show = await getShowInfo(); diff --git a/src/pages/episodes-index.html.md.ts b/packages/starpod/src/pages/episodes-index.html.md.ts similarity index 100% rename from src/pages/episodes-index.html.md.ts rename to packages/starpod/src/pages/episodes-index.html.md.ts diff --git a/src/pages/for-llms.astro b/packages/starpod/src/pages/for-llms.astro similarity index 99% rename from src/pages/for-llms.astro rename to packages/starpod/src/pages/for-llms.astro index f825d6c3..09527a52 100644 --- a/src/pages/for-llms.astro +++ b/packages/starpod/src/pages/for-llms.astro @@ -2,7 +2,7 @@ import Layout from '../layouts/Layout.astro'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; import { formatDate, formatDuration } from '../lib/llms'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; const show = await getShowInfo(); const episodes = await getAllEpisodes(); diff --git a/src/pages/for-llms.html.md.ts b/packages/starpod/src/pages/for-llms.html.md.ts similarity index 90% rename from src/pages/for-llms.html.md.ts rename to packages/starpod/src/pages/for-llms.html.md.ts index a9fedf28..eeef10c1 100644 --- a/src/pages/for-llms.html.md.ts +++ b/packages/starpod/src/pages/for-llms.html.md.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateForLlmsMarkdown } from '../lib/llms'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async ({ site }) => { const show = await getShowInfo(); diff --git a/src/pages/index.astro b/packages/starpod/src/pages/index.astro similarity index 87% rename from src/pages/index.astro rename to packages/starpod/src/pages/index.astro index 6a4f6489..57577192 100644 --- a/src/pages/index.astro +++ b/packages/starpod/src/pages/index.astro @@ -1,6 +1,6 @@ --- import Layout from '../layouts/Layout.astro'; -import EpisodeList from '../components/EpisodeList.astro'; +import EpisodeList from 'virtual:starpod/components/EpisodeList'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; const episodes = await getAllEpisodes(); const show = await getShowInfo(); diff --git a/src/pages/index.html.md.ts b/packages/starpod/src/pages/index.html.md.ts similarity index 90% rename from src/pages/index.html.md.ts rename to packages/starpod/src/pages/index.html.md.ts index 24c8856c..c6b33ae4 100644 --- a/src/pages/index.html.md.ts +++ b/packages/starpod/src/pages/index.html.md.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateHomeMarkdown } from '../lib/llms'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async ({ site }) => { const show = await getShowInfo(); diff --git a/src/pages/llms.txt.ts b/packages/starpod/src/pages/llms.txt.ts similarity index 92% rename from src/pages/llms.txt.ts rename to packages/starpod/src/pages/llms.txt.ts index 9b6767e1..39e1125a 100644 --- a/src/pages/llms.txt.ts +++ b/packages/starpod/src/pages/llms.txt.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateLlmsTxt } from '../lib/llms'; import { getAllEpisodes, getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async ({ site }) => { const show = await getShowInfo(); diff --git a/src/pages/openapi.json.ts b/packages/starpod/src/pages/openapi.json.ts similarity index 89% rename from src/pages/openapi.json.ts rename to packages/starpod/src/pages/openapi.json.ts index bf4d506c..34b1e8b4 100644 --- a/src/pages/openapi.json.ts +++ b/packages/starpod/src/pages/openapi.json.ts @@ -2,7 +2,7 @@ import type { APIRoute } from 'astro'; import { generateOpenApiSpec } from '../lib/openapi'; import { getShowInfo } from '../lib/rss'; -import starpodConfig from '../../starpod.config'; +import starpodConfig from 'virtual:starpod/config'; export const GET: APIRoute = async ({ site }) => { const show = await getShowInfo(); diff --git a/src/pages/robots.txt.ts b/packages/starpod/src/pages/robots.txt.ts similarity index 100% rename from src/pages/robots.txt.ts rename to packages/starpod/src/pages/robots.txt.ts diff --git a/src/styles/buttons.css b/packages/starpod/src/styles/buttons.css similarity index 100% rename from src/styles/buttons.css rename to packages/starpod/src/styles/buttons.css diff --git a/src/styles/global.css b/packages/starpod/src/styles/global.css similarity index 100% rename from src/styles/global.css rename to packages/starpod/src/styles/global.css diff --git a/src/styles/gradient-icon.css b/packages/starpod/src/styles/gradient-icon.css similarity index 100% rename from src/styles/gradient-icon.css rename to packages/starpod/src/styles/gradient-icon.css diff --git a/src/styles/search-icon.css b/packages/starpod/src/styles/search-icon.css similarity index 72% rename from src/styles/search-icon.css rename to packages/starpod/src/styles/search-icon.css index 33a130b4..b1e8f090 100644 --- a/src/styles/search-icon.css +++ b/packages/starpod/src/styles/search-icon.css @@ -1,5 +1,5 @@ .search-icon { - mask-image: url(/images/search-icon.svg); + mask-image: url('../assets/search-icon.svg'); mask-size: contain; mask-repeat: no-repeat; mask-position: center; diff --git a/src/styles/tailwind.css b/packages/starpod/src/styles/tailwind.css similarity index 82% rename from src/styles/tailwind.css rename to packages/starpod/src/styles/tailwind.css index fdd85d82..dcb26567 100644 --- a/src/styles/tailwind.css +++ b/packages/starpod/src/styles/tailwind.css @@ -1,11 +1,20 @@ @import 'tailwindcss'; +/* + Tailwind's automatic source detection skips node_modules, so when this + package is installed as a dependency its own templates must be registered + explicitly. Paths are relative to this stylesheet. +*/ +@source '../components'; +@source '../layouts'; +@source '../pages'; + @plugin '@tailwindcss/forms'; @plugin '@tailwindcss/typography'; @theme { - --background-image-dots-dark: url('/images/dots-dark.svg'); - --background-image-dots-light: url('/images/dots-light.svg'); + --background-image-dots-dark: url('../assets/dots-dark.svg'); + --background-image-dots-light: url('../assets/dots-light.svg'); --background-image-gradient-light: background: radial-gradient( 77% 77% at 50% 0%, #edecfc 0%, diff --git a/src/svgs/404-dark.svg b/packages/starpod/src/svgs/404-dark.svg similarity index 100% rename from src/svgs/404-dark.svg rename to packages/starpod/src/svgs/404-dark.svg diff --git a/src/svgs/404-light.svg b/packages/starpod/src/svgs/404-light.svg similarity index 100% rename from src/svgs/404-light.svg rename to packages/starpod/src/svgs/404-light.svg diff --git a/src/svgs/apple.svg b/packages/starpod/src/svgs/apple.svg similarity index 100% rename from src/svgs/apple.svg rename to packages/starpod/src/svgs/apple.svg diff --git a/src/svgs/overcast.svg b/packages/starpod/src/svgs/overcast.svg similarity index 100% rename from src/svgs/overcast.svg rename to packages/starpod/src/svgs/overcast.svg diff --git a/src/svgs/pocket-casts.svg b/packages/starpod/src/svgs/pocket-casts.svg similarity index 100% rename from src/svgs/pocket-casts.svg rename to packages/starpod/src/svgs/pocket-casts.svg diff --git a/src/svgs/spotify.svg b/packages/starpod/src/svgs/spotify.svg similarity index 100% rename from src/svgs/spotify.svg rename to packages/starpod/src/svgs/spotify.svg diff --git a/src/svgs/ufo-dark.svg b/packages/starpod/src/svgs/ufo-dark.svg similarity index 100% rename from src/svgs/ufo-dark.svg rename to packages/starpod/src/svgs/ufo-dark.svg diff --git a/src/svgs/ufo-light.svg b/packages/starpod/src/svgs/ufo-light.svg similarity index 100% rename from src/svgs/ufo-light.svg rename to packages/starpod/src/svgs/ufo-light.svg diff --git a/src/svgs/youtube.svg b/packages/starpod/src/svgs/youtube.svg similarity index 100% rename from src/svgs/youtube.svg rename to packages/starpod/src/svgs/youtube.svg diff --git a/packages/starpod/src/utils/config.ts b/packages/starpod/src/utils/config.ts new file mode 100644 index 00000000..3a4ba681 --- /dev/null +++ b/packages/starpod/src/utils/config.ts @@ -0,0 +1,161 @@ +import { + array, + minLength, + nonEmpty, + object, + optional, + pipe, + safeParse, + string, + url, + type GenericSchema +} from 'valibot'; + +export type Host = { + /** + * The name of the host + */ + name: string; + /** + * A bio about the host to display + */ + bio: string; + /** + * A url to an image to use for the host + */ + img: string; + /** + * A url to the host's GitHub + */ + github?: string; + /** + * A url to the host's Twitter + */ + twitter?: string; + /** + * A url to the host's website + */ + website?: string; +}; + +export type NavLink = { + /** + * Link text, e.g. "Store". + */ + label: string; + /** + * Destination: an external URL or a local path like "/sponsor" for a + * custom page you added to your site's src/pages/. + */ + href: string; +}; + +export type StarpodConfig = { + /** + * A very short tagline for your show. Generally, no more than one sentence. Less is more here. + */ + blurb: string; + /** + * A somewhat longer description of what your show is about. This should still ideally be fairly short, and should usually be 2-4 sentences. + */ + description: string; + /** + * A list of your show's hosts and their info. + */ + hosts: Array; + /** + * Links to the platforms your show is available on. + */ + platforms: { + apple?: string; + appleIdNumber?: string; + overcast?: string; + pocketCasts?: string; + spotify?: string; + youtube?: string; + }; + /** + * The url to the RSS feed where your podcast is hosted. + */ + rssFeed: string; + /** + * Extra navigation links shown after About and Contact β€” e.g. a merch + * store, or custom pages your site adds (like a sponsor page). + */ + links?: Array; +}; + +const urlField = (name: string) => + pipe(string(`${name} must be a string`), url(`${name} must be a valid URL`)); + +const HostSchema: GenericSchema = object({ + name: pipe(string(), nonEmpty('every host needs a name')), + bio: string('every host needs a bio (an empty string is fine)'), + img: pipe( + string(), + nonEmpty( + "every host needs an img β€” a filename inside your site's src/img/people/" + ) + ), + github: optional(urlField('host github')), + twitter: optional(urlField('host twitter')), + website: optional(urlField('host website')) +}); + +const NavLinkSchema: GenericSchema = object({ + label: pipe(string(), nonEmpty('every link needs a label')), + href: pipe(string(), nonEmpty('every link needs an href')) +}); + +const StarpodConfigSchema: GenericSchema = object({ + blurb: pipe( + string('blurb must be a string'), + nonEmpty('blurb is required β€” a one-line tagline for your show') + ), + description: pipe( + string('description must be a string'), + nonEmpty('description is required β€” a few sentences about your show') + ), + hosts: pipe( + array(HostSchema, 'hosts must be an array'), + minLength(1, 'add at least one host') + ), + platforms: object( + { + apple: optional(urlField('platforms.apple')), + appleIdNumber: optional(string()), + overcast: optional(urlField('platforms.overcast')), + pocketCasts: optional(urlField('platforms.pocketCasts')), + spotify: optional(urlField('platforms.spotify')), + youtube: optional(urlField('platforms.youtube')) + }, + 'platforms is required (an empty object is fine)' + ), + rssFeed: urlField('rssFeed'), + links: optional(array(NavLinkSchema, 'links must be an array')) +}); + +/** + * Validates a Starpod config, throwing a readable error listing every + * problem. Runs both from `defineStarpodConfig` (so mistakes surface the + * moment the config file loads) and inside the integration (so a config + * that skipped the helper is still checked). + */ +export function validateStarpodConfig(config: StarpodConfig): StarpodConfig { + const result = safeParse(StarpodConfigSchema, config); + if (!result.success) { + const problems = result.issues + .map((issue) => { + const path = issue.path?.map((segment) => segment.key).join('.'); + return ` - ${path ? `${path}: ` : ''}${issue.message}`; + }) + .join('\n'); + throw new Error( + `[starpod] Invalid Starpod config:\n${problems}\n\nSee https://github.com/shipshapecode/starpod#configuration for the full reference.` + ); + } + return result.output; +} + +export const defineStarpodConfig = (config: StarpodConfig) => + validateStarpodConfig(config); diff --git a/src/utils/dasherize.ts b/packages/starpod/src/utils/dasherize.ts similarity index 100% rename from src/utils/dasherize.ts rename to packages/starpod/src/utils/dasherize.ts diff --git a/src/utils/truncate.ts b/packages/starpod/src/utils/truncate.ts similarity index 100% rename from src/utils/truncate.ts rename to packages/starpod/src/utils/truncate.ts diff --git a/packages/starpod/src/vercel-md-negotiation.d.mts b/packages/starpod/src/vercel-md-negotiation.d.mts new file mode 100644 index 00000000..bc6112db --- /dev/null +++ b/packages/starpod/src/vercel-md-negotiation.d.mts @@ -0,0 +1,27 @@ +interface VercelRoute { + src?: string; + dest?: string; + headers?: Record; + status?: number; + handle?: string; + has?: Array<{ type: string; key: string; value: string }>; + continue?: boolean; +} + +interface NegotiationRoute extends VercelRoute { + src: string; +} + +interface VercelOutputConfig { + routes: VercelRoute[]; + [key: string]: unknown; +} + +export function collectMarkdownPaths(staticDir: string): string[]; +export function pagesToMarkdownPaths(pathnames: string[]): string[]; +export function buildNegotiationRoutes(mdPaths: string[]): NegotiationRoute[]; +export function patchConfig( + config: VercelOutputConfig, + mdPaths: string[] +): { config: VercelOutputConfig; inserted: number }; +export function main(outputDir?: string, mdPaths?: string[] | null): void; diff --git a/scripts/vercel-md-negotiation.mjs b/packages/starpod/src/vercel-md-negotiation.mjs similarity index 89% rename from scripts/vercel-md-negotiation.mjs rename to packages/starpod/src/vercel-md-negotiation.mjs index ca18f9b3..cf3bac45 100644 --- a/scripts/vercel-md-negotiation.mjs +++ b/packages/starpod/src/vercel-md-negotiation.mjs @@ -54,6 +54,20 @@ export function collectMarkdownPaths(staticDir) { return paths.sort(); } +/** + * Derive negotiated URL paths from Astro's generated page pathnames (as given + * to the `astro:build:done` hook), e.g. 'about.html.md' -> '/about'. Used by + * the integration, where the static output directory is not yet populated at + * hook time. + */ +export function pagesToMarkdownPaths(pathnames) { + return pathnames + .map((p) => p.replace(/^\/+/, '').replace(/\/+$/, '')) + .filter((p) => p.endsWith(MD_SUFFIX)) + .map((p) => '/' + p.slice(0, -MD_SUFFIX.length)) + .sort(); +} + const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const chunk = (items, size) => { @@ -171,11 +185,11 @@ export function patchConfig(config, mdPaths) { return { config: { ...config, routes }, inserted: negotiationRoutes.length }; } -export function main(outputDir = '.vercel/output') { +export function main(outputDir = '.vercel/output', mdPaths = null) { const configPath = join(outputDir, 'config.json'); const staticDir = join(outputDir, 'static'); - if (!existsSync(configPath) || !existsSync(staticDir)) { + if (!existsSync(configPath) || (mdPaths === null && !existsSync(staticDir))) { console.log( `[md-negotiation] No Vercel build output at ${outputDir}, skipping` ); @@ -183,7 +197,9 @@ export function main(outputDir = '.vercel/output') { } const config = JSON.parse(readFileSync(configPath, 'utf-8')); - const mdPaths = collectMarkdownPaths(staticDir); + if (mdPaths === null) { + mdPaths = collectMarkdownPaths(staticDir); + } const { config: patched, inserted } = patchConfig(config, mdPaths); if (inserted === 0) { diff --git a/packages/starpod/src/virtual.d.ts b/packages/starpod/src/virtual.d.ts new file mode 100644 index 00000000..01018735 --- /dev/null +++ b/packages/starpod/src/virtual.d.ts @@ -0,0 +1,16 @@ +declare module 'virtual:starpod/config' { + const config: import('./utils/config').StarpodConfig; + export default config; + /** Whether the guests/sponsors database was enabled via integration options. */ + export const database: boolean; +} + +declare module 'virtual:starpod/user-css' { + const noop: Record; + export default noop; +} + +declare module 'virtual:starpod/components/*' { + const Component: (props: Record) => unknown; + export default Component; +} diff --git a/packages/starpod/tsconfig.json b/packages/starpod/tsconfig.json new file mode 100644 index 00000000..4d3cf63a --- /dev/null +++ b/packages/starpod/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "strictNullChecks": true, + "verbatimModuleSyntax": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "include": ["src/**/*"] +} diff --git a/playwright.worktree.config.ts b/playwright.worktree.config.ts new file mode 100644 index 00000000..d01db439 --- /dev/null +++ b/playwright.worktree.config.ts @@ -0,0 +1,23 @@ +// Worktree-only override: the user's own dev server occupies :4321 serving +// the main branch, so e2e here must boot its own server on another port. +// Not meant to be committed. +import { defineConfig } from '@playwright/test'; + +import baseConfig from './playwright.config'; + +const PORT = 4399; + +export default defineConfig({ + ...baseConfig, + reporter: 'list', + use: { + ...baseConfig.use, + baseURL: `http://localhost:${PORT}` + }, + webServer: { + command: `pnpm dev --port ${PORT}`, + url: `http://localhost:${PORT}`, + reuseExistingServer: false, + timeout: 180 * 1000 + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 389b06ca..b660fd05 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,43 +16,25 @@ importers: version: 5.1.4(@babel/core@7.29.7)(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(preact@10.29.2)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0) '@astrojs/vercel': specifier: ^10.0.8 - version: 10.0.8(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(react@19.0.0)(rollup@4.60.4) + version: 10.0.8(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(encoding@0.1.13)(react@19.0.0)(rollup@4.60.4) '@bryanguffey/astro-standard-site': specifier: ^1.0.3 version: 1.0.3(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0)) - '@libsql/client': - specifier: ^0.17.3 - version: 0.17.3 - '@preact/signals': - specifier: ^2.9.1 - version: 2.9.1(preact@10.29.2) - '@vercel/analytics': - specifier: ^1.6.1 - version: 1.6.1(react@19.0.0) - '@vercel/speed-insights': - specifier: ^1.3.1 - version: 1.3.1(react@19.0.0) astro: specifier: 6.4.2 version: 6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0) - astro-seo-schema: - specifier: ^5.2.0 - version: 5.2.0(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(schema-dts@1.1.5) - atropos: - specifier: ^2.0.2 - version: 2.0.2 drizzle-orm: specifier: ^0.45.2 version: 0.45.2(@libsql/client@0.17.3) - preact: - specifier: ^10.29.2 - version: 10.29.2 + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 rss-to-json: specifier: ^2.1.1 version: 2.1.1 - schema-dts: - specifier: ^1.1.5 - version: 1.1.5 + starpod: + specifier: workspace:* + version: link:packages/starpod valibot: specifier: ^1.4.1 version: 1.4.1(typescript@5.9.3) @@ -60,24 +42,12 @@ importers: '@astrojs/check': specifier: ^0.9.9 version: 0.9.9(prettier-plugin-astro@0.14.1)(prettier@3.8.3)(typescript@5.9.3) - '@astrojs/sitemap': - specifier: ^3.7.3 - version: 3.7.3 '@eslint/js': specifier: ^9.39.4 version: 9.39.4 '@playwright/test': specifier: ^1.60.0 version: 1.60.0 - '@tailwindcss/forms': - specifier: ^0.5.11 - version: 0.5.11(tailwindcss@4.3.0) - '@tailwindcss/typography': - specifier: ^0.5.19 - version: 0.5.19(tailwindcss@4.3.0) - '@tailwindcss/vite': - specifier: ^4.3.0 - version: 4.3.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)(yaml@2.9.0)) '@testing-library/jest-dom': specifier: ^6.9.1 version: 6.9.1 @@ -111,12 +81,12 @@ importers: globals: specifier: ^16.5.0 version: 16.5.0 - html-to-text: - specifier: ^9.0.5 - version: 9.0.5 jsdom: specifier: ^27.4.0 version: 27.4.0 + preact: + specifier: ^10.29.2 + version: 10.29.2 prettier: specifier: ^3.8.3 version: 3.8.3 @@ -126,6 +96,9 @@ importers: prettier-plugin-tailwindcss: specifier: ^0.7.4 version: 0.7.4(prettier-plugin-astro@0.14.1)(prettier@3.8.3) + release-plan: + specifier: ^0.18.0 + version: 0.18.0 sharp: specifier: ^0.34.5 version: 0.34.5 @@ -145,6 +118,73 @@ importers: specifier: ^4.1.7 version: 4.1.7(@types/node@24.12.4)(jsdom@27.4.0)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)(yaml@2.9.0)) + packages/starpod: + dependencies: + '@astrojs/sitemap': + specifier: ^3.7.3 + version: 3.7.3 + '@bryanguffey/astro-standard-site': + specifier: ^1.0.3 + version: 1.0.3(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0)) + '@libsql/client': + specifier: ^0.17.3 + version: 0.17.3 + '@preact/signals': + specifier: ^2.9.1 + version: 2.9.1(preact@10.29.2) + '@tailwindcss/forms': + specifier: ^0.5.11 + version: 0.5.11(tailwindcss@4.3.0) + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.3.0) + '@tailwindcss/vite': + specifier: ^4.3.0 + version: 4.3.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)(yaml@2.9.0)) + '@vercel/speed-insights': + specifier: ^1.3.1 + version: 1.3.1(react@19.0.0) + astro-seo-schema: + specifier: ^5.2.0 + version: 5.2.0(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(schema-dts@1.1.5) + atropos: + specifier: ^2.0.2 + version: 2.0.2 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2(@libsql/client@0.17.3) + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 + rss-to-json: + specifier: ^2.1.1 + version: 2.1.1 + schema-dts: + specifier: ^1.1.5 + version: 1.1.5 + tailwindcss: + specifier: ^4.3.0 + version: 4.3.0 + valibot: + specifier: ^1.4.1 + version: 1.4.1(typescript@5.9.3) + devDependencies: + '@astrojs/preact': + specifier: ^5.1.4 + version: 5.1.4(@babel/core@7.29.7)(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(preact@10.29.2)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0) + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 + astro: + specifier: 6.4.2 + version: 6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0) + preact: + specifier: ^10.29.2 + version: 10.29.2 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages: '@acemir/cssom@0.9.31': @@ -1079,6 +1119,9 @@ packages: '@noble/hashes': optional: true + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -1236,6 +1279,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -1313,6 +1360,18 @@ packages: cpu: [x64] os: [win32] + '@manypkg/find-root@2.2.3': + resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==} + engines: {node: '>=14.18.0'} + + '@manypkg/get-packages@2.2.2': + resolution: {integrity: sha512-3+Zd8kLZmsyJFmWTBtY0MAuCErI7yKB2cjMBlujvSVKZ2R/BMXi0kjCXu2dtRlSq/ML86t1FkumT0yreQ3n8OQ==} + engines: {node: '>=14.18.0'} + + '@manypkg/tools@1.1.2': + resolution: {integrity: sha512-3lBouSuF7CqlseLB+FKES0K4FQ02JrbEoRtJhxnsyB1s5v4AP03gsoohN8jp7DcOImhaR9scYdztq3/sLfk/qQ==} + engines: {node: '>=14.18.0'} + '@mapbox/node-pre-gyp@2.0.3': resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} engines: {node: '>=18'} @@ -1333,9 +1392,91 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npmcli/fs@1.1.1': + resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} + + '@npmcli/git@6.0.3': + resolution: {integrity: sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/move-file@1.1.2': + resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} + engines: {node: '>=10'} + deprecated: This functionality has been moved to @npmcli/fs + + '@npmcli/package-json@6.2.0': + resolution: {integrity: sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/promise-spawn@8.0.3': + resolution: {integrity: sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@octokit/auth-token@5.1.2': + resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} + engines: {node: '>= 18'} + + '@octokit/core@6.1.6': + resolution: {integrity: sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==} + engines: {node: '>= 18'} + + '@octokit/endpoint@10.1.4': + resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} + engines: {node: '>= 18'} + + '@octokit/graphql@8.2.2': + resolution: {integrity: sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + + '@octokit/plugin-paginate-rest@11.6.0': + resolution: {integrity: sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@5.3.1': + resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@13.5.0': + resolution: {integrity: sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@6.1.8': + resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} + engines: {node: '>= 18'} + + '@octokit/request@9.2.4': + resolution: {integrity: sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==} + engines: {node: '>= 18'} + + '@octokit/rest@21.1.1': + resolution: {integrity: sha512-sTQV7va0IUVZcntzy1q3QqPm/r8rWtDCqpRAmb8eXXnKkjoQEtFe3Nt5GTVsHft+R6jJoHeSiVLcgcvhtue/rg==} + engines: {node: '>= 18'} + + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -1345,6 +1486,18 @@ packages: engines: {node: '>=18'} hasBin: true + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@3.0.3': + resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} + engines: {node: '>=12'} + '@preact/preset-vite@2.10.5': resolution: {integrity: sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==} peerDependencies: @@ -1514,6 +1667,9 @@ packages: cpu: [x64] os: [win32] + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} @@ -1548,6 +1704,10 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1665,6 +1825,10 @@ packages: peerDependencies: preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1905,6 +2069,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -1923,6 +2095,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1931,6 +2107,13 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1955,6 +2138,9 @@ packages: array-iterate@2.0.1: resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2024,6 +2210,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + before-after-hook@3.0.2: + resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} @@ -2036,6 +2225,9 @@ packages: brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} @@ -2052,6 +2244,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2082,6 +2278,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2099,6 +2299,10 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -2107,6 +2311,18 @@ packages: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2145,6 +2361,9 @@ packages: engines: {node: '>=18'} hasBin: true + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -2220,6 +2439,10 @@ packages: resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} engines: {node: '>= 0.4'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2394,6 +2617,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + electron-to-chromium@1.5.364: resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} @@ -2403,6 +2629,12 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + enhanced-resolve@5.22.1: resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} engines: {node: '>=10.13.0'} @@ -2423,6 +2655,9 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -2508,6 +2743,7 @@ packages: eslint@9.39.4: resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2544,6 +2780,14 @@ packages: eventemitter3@5.0.4: resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -2551,6 +2795,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2592,6 +2839,10 @@ packages: picomatch: optional: true + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2638,10 +2889,25 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + fs-extra@11.4.0: + resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} + engines: {node: '>=14.14'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2674,6 +2940,14 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} @@ -2681,6 +2955,11 @@ packages: resolution: {integrity: sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==} engines: {node: '>=20.20.0'} + github-changelog@2.1.4: + resolution: {integrity: sha512-mZQF/YC9OR8XMGpYlLQqG66RiKwlaQZ7rXTZug28oOYkzOXd0acszuvYHVzPlYmns1aDDSwQkbzFxNOYpWhmig==} + engines: {node: 18.* || 20.* || >= 22} + hasBin: true + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -2692,10 +2971,19 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -2708,6 +2996,9 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2771,6 +3062,17 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + hosted-git-info@8.1.0: + resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} + engines: {node: ^18.17.0 || >=20.5.0} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -2791,6 +3093,10 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2803,6 +3109,21 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2819,10 +3140,31 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ini@5.0.0: + resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} + engines: {node: ^18.17.0 || >=20.5.0} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ip-address@10.5.0: + resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} + engines: {node: '>= 12'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -2877,6 +3219,9 @@ packages: engines: {node: '>=14.16'} hasBin: true + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -2908,6 +3253,14 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -2916,6 +3269,10 @@ packages: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -2934,13 +3291,23 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + iso-datestring-validator@2.2.2: resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -2968,6 +3335,10 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@4.0.0: + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + engines: {node: ^18.17.0 || >=20.5.0} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -2988,6 +3359,9 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2998,6 +3372,14 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + ky@1.14.3: + resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==} + engines: {node: '>=18'} + + latest-version@9.0.0: + resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} + engines: {node: '>=18'} + leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} @@ -3090,6 +3472,9 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.1: resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} engines: {node: 20 || >=22} @@ -3097,6 +3482,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -3107,6 +3496,10 @@ packages: magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -3159,6 +3552,9 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -3259,6 +3655,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -3274,14 +3674,58 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -3295,6 +3739,9 @@ packages: multiformats@9.9.0: resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -3303,6 +3750,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + neotraverse@0.6.18: resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} engines: {node: '>= 10'} @@ -3345,9 +3796,37 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-install-checks@7.1.2: + resolution: {integrity: sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-package-arg@12.0.2: + resolution: {integrity: sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-pick-manifest@10.0.0: + resolution: {integrity: sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -3373,6 +3852,13 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + oniguruma-parser@0.12.2: resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} @@ -3395,6 +3881,14 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + p-queue@9.3.0: resolution: {integrity: sha512-7NED7xhQ74Ngp4JP/2e0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P03YoTKa9dDXyZ3r6P616gUXttrB6nnHYKang==} engines: {node: '>=20'} @@ -3403,6 +3897,13 @@ packages: resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} engines: {node: '>=20'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-json@10.0.1: + resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} + engines: {node: '>=18'} + package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} @@ -3410,9 +3911,25 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-github-repo-url@1.4.1: + resolution: {integrity: sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==} + parse-latin@7.0.0: resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -3429,10 +3946,22 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -3569,16 +4098,43 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + proc-log@5.0.0: + resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + promise-limit@2.7.0: resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -3593,6 +4149,10 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -3625,6 +4185,14 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} + engines: {node: '>=14'} + + registry-url@6.0.1: + resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} + engines: {node: '>=12'} + rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} @@ -3637,6 +4205,10 @@ packages: rehype@13.0.2: resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + release-plan@0.18.0: + resolution: {integrity: sha512-WzP+O+XRF4AqhTDQK84FovY+TxHyC8J7QWoOwxnrvVjyHns417l1FUldboRhBYua5Pej6Yg/2jH1dEH2MRNHeA==} + hasBin: true + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -3690,10 +4262,19 @@ packages: retext@9.0.0: resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rollup@4.60.4: resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3715,6 +4296,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-formatter@0.7.9: resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} @@ -3788,6 +4372,13 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-code-frame@1.3.0: resolution: {integrity: sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==} @@ -3799,10 +4390,22 @@ packages: engines: {node: '>=20.19.5', npm: '>=10.8.2'} hasBin: true + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + smol-toml@1.6.1: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} + socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3821,6 +4424,22 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + + ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + stack-trace@1.0.0: resolution: {integrity: sha512-H6D7134xi6qONvh7ZHKgviXf+rd3vhGBSvebPZCaUkd8zvQ+7PtDw6CljPTe4cXWNf2IKZGNqw6VJXSb9IgBpA==} engines: {node: '>=20.0.0'} @@ -3842,6 +4461,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -3849,10 +4472,26 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -3890,10 +4529,22 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + tar@7.5.15: resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} engines: {node: '>=18'} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -3999,12 +4650,22 @@ packages: unicode-segmenter@0.14.5: resolution: {integrity: sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} unifont@0.7.4: resolution: {integrity: sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==} + unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + + unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} + unist-util-find-after@5.0.0: resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} @@ -4032,6 +4693,13 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unstorage@1.17.5: resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} peerDependencies: @@ -4114,6 +4782,13 @@ packages: typescript: optional: true + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@6.0.2: + resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} + engines: {node: ^18.17.0 || >=20.5.0} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -4359,6 +5034,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -4372,6 +5052,13 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -4401,6 +5088,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -4419,6 +5109,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4427,6 +5121,10 @@ packages: resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -4439,6 +5137,10 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -4594,12 +5296,12 @@ snapshots: is-wsl: 3.1.1 which-pm-runs: 1.1.0 - '@astrojs/vercel@10.0.8(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(react@19.0.0)(rollup@4.60.4)': + '@astrojs/vercel@10.0.8(astro@6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0))(encoding@0.1.13)(react@19.0.0)(rollup@4.60.4)': dependencies: '@astrojs/internal-helpers': 0.10.0 '@vercel/analytics': 1.6.1(react@19.0.0) '@vercel/functions': 3.6.1 - '@vercel/nft': 1.10.2(rollup@4.60.4) + '@vercel/nft': 1.10.2(encoding@0.1.13)(rollup@4.60.4) '@vercel/routing-utils': 5.3.3 astro: 6.4.2(@types/node@24.12.4)(@vercel/functions@3.6.1)(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.60.4)(tsx@4.22.4)(yaml@2.9.0) esbuild: 0.27.7 @@ -5245,6 +5947,8 @@ snapshots: '@exodus/bytes@1.15.1': {} + '@gar/promisify@1.1.3': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -5357,6 +6061,15 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.3 @@ -5438,12 +6151,27 @@ snapshots: '@libsql/win32-x64-msvc@0.5.29': optional: true - '@mapbox/node-pre-gyp@2.0.3': + '@manypkg/find-root@2.2.3': + dependencies: + '@manypkg/tools': 1.1.2 + + '@manypkg/get-packages@2.2.2': + dependencies: + '@manypkg/find-root': 2.2.3 + '@manypkg/tools': 1.1.2 + + '@manypkg/tools@1.1.2': + dependencies: + fast-glob: 3.3.3 + jju: 1.4.0 + js-yaml: 4.1.1 + + '@mapbox/node-pre-gyp@2.0.3(encoding@0.1.13)': dependencies: consola: 3.4.2 detect-libc: 2.1.2 https-proxy-agent: 7.0.6 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) nopt: 8.1.0 semver: 7.8.1 tar: 7.5.15 @@ -5465,14 +6193,132 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@npmcli/fs@1.1.1': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.8.1 + + '@npmcli/git@6.0.3': + dependencies: + '@npmcli/promise-spawn': 8.0.3 + ini: 5.0.0 + lru-cache: 10.4.3 + npm-pick-manifest: 10.0.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 + semver: 7.8.1 + which: 5.0.0 + + '@npmcli/move-file@1.1.2': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + + '@npmcli/package-json@6.2.0': + dependencies: + '@npmcli/git': 6.0.3 + glob: 10.5.0 + hosted-git-info: 8.1.0 + json-parse-even-better-errors: 4.0.0 + proc-log: 5.0.0 + semver: 7.8.1 + validate-npm-package-license: 3.0.4 + + '@npmcli/promise-spawn@8.0.3': + dependencies: + which: 5.0.0 + + '@octokit/auth-token@5.1.2': {} + + '@octokit/core@6.1.6': + dependencies: + '@octokit/auth-token': 5.1.2 + '@octokit/graphql': 8.2.2 + '@octokit/request': 9.2.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + before-after-hook: 3.0.2 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@10.1.4': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@8.2.2': + dependencies: + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@24.2.0': {} + + '@octokit/openapi-types@25.1.0': {} + + '@octokit/plugin-paginate-rest@11.6.0(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/types': 13.10.0 + + '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + + '@octokit/plugin-rest-endpoint-methods@13.5.0(@octokit/core@6.1.6)': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/types': 13.10.0 + + '@octokit/request-error@6.1.8': + dependencies: + '@octokit/types': 14.1.0 + + '@octokit/request@9.2.4': + dependencies: + '@octokit/endpoint': 10.1.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.3 + + '@octokit/rest@21.1.1': + dependencies: + '@octokit/core': 6.1.6 + '@octokit/plugin-paginate-rest': 11.6.0(@octokit/core@6.1.6) + '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.6) + '@octokit/plugin-rest-endpoint-methods': 13.5.0(@octokit/core@6.1.6) + + '@octokit/types@13.10.0': + dependencies: + '@octokit/openapi-types': 24.2.0 + + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + '@oslojs/encoding@1.1.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@pkgr/core@0.3.6': {} '@playwright/test@1.60.0': dependencies: playwright: 1.60.0 + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@3.0.3': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + '@preact/preset-vite@2.10.5(@babel/core@7.29.7)(preact@10.29.2)(rollup@4.60.4)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.22.4)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 @@ -5607,6 +6453,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.4': optional: true + '@sec-ant/readable-stream@0.4.1': {} + '@selderee/plugin-htmlparser2@0.11.0': dependencies: domhandler: 5.0.3 @@ -5652,6 +6500,8 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} '@tailwindcss/forms@0.5.11(tailwindcss@4.3.0)': @@ -5757,6 +6607,8 @@ snapshots: '@testing-library/dom': 8.20.1 preact: 10.29.2 + '@tootallnate/once@1.1.2': {} + '@types/aria-query@5.0.4': {} '@types/chai@5.2.3': @@ -5868,9 +6720,9 @@ snapshots: dependencies: '@vercel/oidc': 3.5.0 - '@vercel/nft@1.10.2(rollup@4.60.4)': + '@vercel/nft@1.10.2(encoding@0.1.13)(rollup@4.60.4)': dependencies: - '@mapbox/node-pre-gyp': 2.0.3 + '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) '@rollup/pluginutils': 5.4.0(rollup@4.60.4) acorn: 8.16.0 acorn-import-attributes: 1.9.5(acorn@8.16.0) @@ -6011,6 +6863,15 @@ snapshots: agent-base@7.1.4: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + ajv-draft-04@1.0.0(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -6031,12 +6892,18 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.3.0: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -6059,6 +6926,8 @@ snapshots: array-iterate@2.0.1: {} + assert-never@1.4.0: {} + assertion-error@2.0.1: {} astro-eslint-parser@1.4.0: @@ -6217,6 +7086,8 @@ snapshots: baseline-browser-mapping@2.10.33: {} + before-after-hook@3.0.2: {} + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 @@ -6232,6 +7103,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -6250,6 +7125,29 @@ snapshots: buffer-from@1.1.2: {} + cacache@15.3.0: + dependencies: + '@npmcli/fs': 1.1.1 + '@npmcli/move-file': 1.1.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 7.2.3 + infer-owner: 1.0.4 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 8.0.1 + tar: 6.2.1 + unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6280,6 +7178,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -6294,10 +7194,29 @@ snapshots: dependencies: readdirp: 5.0.0 + chownr@2.0.0: {} + chownr@3.0.0: {} ci-info@4.4.0: {} + clean-stack@2.2.0: {} + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.2 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -6333,6 +7252,11 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + consola@3.4.2: {} convert-source-map@2.0.0: {} @@ -6422,6 +7346,8 @@ snapshots: which-collection: 1.0.2 which-typed-array: 1.1.21 + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -6501,6 +7427,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + electron-to-chromium@1.5.364: {} emmet@2.4.11: @@ -6510,6 +7438,13 @@ snapshots: emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + enhanced-resolve@5.22.1: dependencies: graceful-fs: 4.2.11 @@ -6523,6 +7458,8 @@ snapshots: entities@8.0.0: {} + err-code@2.0.3: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -6767,10 +7704,39 @@ snapshots: eventemitter3@5.0.4: {} + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.2.0 + expect-type@1.3.0: {} extend@3.0.2: {} + fast-content-type-parse@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -6809,6 +7775,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -6847,6 +7817,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -6855,6 +7830,18 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + fs-extra@11.4.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + fsevents@2.3.2: optional: true @@ -6887,6 +7874,13 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 + get-stream@6.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -6895,6 +7889,21 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-changelog@2.1.4: + dependencies: + '@manypkg/get-packages': 2.2.2 + chalk: 4.1.2 + cli-highlight: 2.1.11 + execa: 5.1.1 + hosted-git-info: 4.1.0 + make-fetch-happen: 9.1.0 + p-map: 3.0.0 + progress: 2.0.3 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -6905,18 +7914,38 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@13.0.6: dependencies: minimatch: 10.2.5 minipass: 7.1.3 path-scurry: 2.0.2 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@14.0.0: {} globals@16.5.0: {} gopd@1.2.0: {} + graceful-fs@4.2.10: {} + graceful-fs@4.2.11: {} h3@1.15.11: @@ -7038,6 +8067,16 @@ snapshots: he@1.2.0: {} + highlight.js@10.7.3: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + hosted-git-info@8.1.0: + dependencies: + lru-cache: 10.4.3 + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.1 @@ -7065,6 +8104,14 @@ snapshots: http-cache-semantics@4.2.0: {} + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -7086,6 +8133,19 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@2.1.0: {} + + human-signals@8.0.1: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ignore@5.3.2: {} import-fresh@3.3.1: @@ -7097,12 +8157,27 @@ snapshots: indent-string@4.0.0: {} + infer-owner@1.0.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@5.0.0: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.4 side-channel: 1.1.0 + ip-address@10.5.0: {} + iron-webcrypto@1.2.1: {} is-arguments@1.2.0: @@ -7148,6 +8223,8 @@ snapshots: dependencies: is-docker: 3.0.0 + is-lambda@1.0.1: {} + is-map@2.0.3: {} is-number-object@1.1.1: @@ -7174,6 +8251,10 @@ snapshots: dependencies: call-bound: 1.0.4 + is-stream@2.0.1: {} + + is-stream@4.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -7185,6 +8266,8 @@ snapshots: has-symbols: 1.1.0 safe-regex-test: 1.1.0 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakset@2.0.4: @@ -7200,10 +8283,20 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.5: {} + iso-datestring-validator@2.2.2: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jiti@2.7.0: {} + jju@1.4.0: {} + js-base64@3.7.8: {} js-tokens@4.0.0: {} @@ -7244,6 +8337,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@4.0.0: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -7256,6 +8351,12 @@ snapshots: jsonc-parser@3.3.1: {} + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -7264,6 +8365,12 @@ snapshots: kolorist@1.8.0: {} + ky@1.14.3: {} + + latest-version@9.0.0: + dependencies: + package-json: 10.0.1 + leac@0.6.0: {} levn@0.4.1: @@ -7343,12 +8450,18 @@ snapshots: longest-streak@3.1.0: {} + lru-cache@10.4.3: {} + lru-cache@11.5.1: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lz-string@1.5.0: {} magic-string@0.30.21: @@ -7361,6 +8474,28 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 + make-fetch-happen@9.1.0: + dependencies: + agentkeepalive: 4.6.0 + cacache: 15.3.0 + http-cache-semantics: 4.2.0 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 1.4.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 6.2.1 + ssri: 8.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + markdown-table@3.0.4: {} math-intrinsics@1.1.0: {} @@ -7489,6 +8624,8 @@ snapshots: mdn-data@2.27.1: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromark-core-commonmark@2.0.3: @@ -7693,6 +8830,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mimic-fn@2.1.0: {} + min-indent@1.0.1: {} mini-svg-data-uri@1.4.4: {} @@ -7705,12 +8844,55 @@ snapshots: dependencies: brace-expansion: 1.1.15 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@1.4.1: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.3: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + minizlib@3.1.0: dependencies: minipass: 7.1.3 + mkdirp@1.0.4: {} + mrmime@2.0.1: {} ms@2.1.3: {} @@ -7719,10 +8901,18 @@ snapshots: multiformats@9.9.0: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.12: {} natural-compare@1.4.0: {} + negotiator@0.6.4: {} + neotraverse@0.6.18: {} nlcst-to-string@4.0.0: @@ -7731,9 +8921,11 @@ snapshots: node-fetch-native@1.6.7: {} - node-fetch@2.7.0: + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 node-gyp-build@4.8.4: {} @@ -7752,10 +8944,41 @@ snapshots: normalize-path@3.0.0: {} + npm-install-checks@7.1.2: + dependencies: + semver: 7.8.1 + + npm-normalize-package-bin@4.0.0: {} + + npm-package-arg@12.0.2: + dependencies: + hosted-git-info: 8.1.0 + proc-log: 5.0.0 + semver: 7.8.1 + validate-npm-package-name: 6.0.2 + + npm-pick-manifest@10.0.0: + dependencies: + npm-install-checks: 7.1.2 + npm-normalize-package-bin: 4.0.0 + npm-package-arg: 12.0.2 + semver: 7.8.1 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + object-inspect@1.13.4: {} object-is@1.1.6: @@ -7784,6 +9007,14 @@ snapshots: ohash@2.0.11: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + oniguruma-parser@0.12.2: {} oniguruma-to-es@4.3.6: @@ -7813,6 +9044,14 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@3.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + p-queue@9.3.0: dependencies: eventemitter3: 5.0.4 @@ -7820,12 +9059,23 @@ snapshots: p-timeout@7.0.1: {} + package-json-from-dist@1.0.1: {} + + package-json@10.0.1: + dependencies: + ky: 1.14.3 + registry-auth-token: 5.1.1 + registry-url: 6.0.1 + semver: 7.8.1 + package-manager-detector@1.6.0: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-github-repo-url@1.4.1: {} + parse-latin@7.0.0: dependencies: '@types/nlcst': 2.0.3 @@ -7835,6 +9085,16 @@ snapshots: unist-util-visit-children: 3.0.0 vfile: 6.0.3 + parse-ms@4.0.0: {} + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -7852,8 +9112,17 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-key@4.0.0: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-scurry@2.0.2: dependencies: lru-cache: 11.5.1 @@ -7929,12 +9198,29 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + prismjs@1.30.0: {} + proc-log@5.0.0: {} + + progress@2.0.3: {} + + promise-inflight@1.0.1: {} + promise-limit@2.7.0: {} + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + property-information@7.1.0: {} + proto-list@1.2.4: {} + proxy-from-env@2.1.0: {} punycode@2.3.1: {} @@ -7943,6 +9229,13 @@ snapshots: radix3@1.1.2: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-is@17.0.2: {} react@19.0.0: @@ -7976,6 +9269,14 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + registry-auth-token@5.1.1: + dependencies: + '@pnpm/npm-conf': 3.0.3 + + registry-url@6.0.1: + dependencies: + rc: 1.2.8 + rehype-parse@9.0.1: dependencies: '@types/hast': 3.0.4 @@ -8001,6 +9302,26 @@ snapshots: rehype-stringify: 10.0.1 unified: 11.0.5 + release-plan@0.18.0: + dependencies: + '@manypkg/get-packages': 2.2.2 + '@npmcli/package-json': 6.2.0 + '@octokit/rest': 21.1.1 + assert-never: 1.4.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + execa: 9.6.1 + fs-extra: 11.4.0 + github-changelog: 2.1.4 + js-yaml: 4.1.1 + latest-version: 9.0.0 + parse-github-repo-url: 1.4.1 + semver: 7.8.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -8081,8 +9402,14 @@ snapshots: retext-stringify: 4.0.0 unified: 11.0.5 + retry@0.12.0: {} + reusify@1.1.0: {} + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + rollup@4.60.4: dependencies: '@types/estree': 1.0.8 @@ -8138,6 +9465,9 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: + optional: true + sass-formatter@0.7.9: dependencies: suf-log: 2.5.3 @@ -8254,6 +9584,10 @@ snapshots: siginfo@2.0.0: {} + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + simple-code-frame@1.3.0: dependencies: kolorist: 1.8.0 @@ -8267,8 +9601,23 @@ snapshots: arg: 5.0.2 sax: 1.6.0 + smart-buffer@4.2.0: {} + smol-toml@1.6.1: {} + socks-proxy-agent@6.2.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.5.0 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -8282,6 +9631,24 @@ snapshots: space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + + ssri@8.0.1: + dependencies: + minipass: 3.3.6 + stack-trace@1.0.0: {} stackback@0.0.2: {} @@ -8301,6 +9668,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -8310,10 +9683,20 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + + strip-final-newline@2.0.0: {} + + strip-final-newline@4.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strnum@1.1.2: {} @@ -8350,6 +9733,15 @@ snapshots: tapable@2.3.3: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + tar@7.5.15: dependencies: '@isaacs/fs-minipass': 4.0.1 @@ -8358,6 +9750,14 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + tiny-inflate@1.0.3: {} tinybench@2.9.0: {} @@ -8439,6 +9839,8 @@ snapshots: unicode-segmenter@0.14.5: {} + unicorn-magic@0.3.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -8455,6 +9857,14 @@ snapshots: ofetch: 1.5.1 ohash: 2.0.11 + unique-filename@1.1.1: + dependencies: + unique-slug: 2.0.2 + + unique-slug@2.0.2: + dependencies: + imurmurhash: 0.1.4 + unist-util-find-after@5.0.0: dependencies: '@types/unist': 3.0.3 @@ -8497,6 +9907,10 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} + + universalify@2.0.1: {} + unstorage@1.17.5(@vercel/functions@3.6.1): dependencies: anymatch: 3.1.3 @@ -8526,6 +9940,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@6.0.2: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -8751,6 +10172,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@5.0.0: + dependencies: + isexe: 3.1.5 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 @@ -8764,6 +10189,14 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + ws@8.21.0: {} xml-name-validator@5.0.0: {} @@ -8776,6 +10209,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yaml-language-server@1.20.0: @@ -8796,10 +10231,22 @@ snapshots: yaml@2.9.0: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yargs@17.7.2: dependencies: cliui: 8.0.1 @@ -8814,6 +10261,8 @@ snapshots: yocto-queue@1.2.2: {} + yoctocolors@2.2.0: {} + zimmerframe@1.1.4: {} zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..924b55f4 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/scripts/publish-atproto-episodes.ts b/scripts/publish-atproto-episodes.ts index 4010972e..c6f47e3b 100644 --- a/scripts/publish-atproto-episodes.ts +++ b/scripts/publish-atproto-episodes.ts @@ -26,7 +26,7 @@ import { } from '@bryanguffey/astro-standard-site'; import starpodConfig from '../starpod.config'; -import { dasherize } from '../src/utils/dasherize'; +import { dasherize } from 'starpod/src/utils/dasherize'; const BACKFILL = process.argv.includes('--backfill'); // Set by the GitHub workflow when it has just triggered a site rebuild: diff --git a/src/components/player/ForwardButton/styles.css b/src/components/player/ForwardButton/styles.css deleted file mode 100644 index e220fe05..00000000 --- a/src/components/player/ForwardButton/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.forward-button { - mask-image: url(/images/forward-icon.svg); -} diff --git a/src/components/player/MuteButton/styles.css b/src/components/player/MuteButton/styles.css deleted file mode 100644 index c78a6361..00000000 --- a/src/components/player/MuteButton/styles.css +++ /dev/null @@ -1,7 +0,0 @@ -.mute-button { - mask-image: url(/images/mute-icon.svg); -} - -.unmute-button { - mask-image: url(/images/unmute-icon.svg); -} diff --git a/src/components/player/RewindButton/styles.css b/src/components/player/RewindButton/styles.css deleted file mode 100644 index c3794419..00000000 --- a/src/components/player/RewindButton/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.rewind-button { - mask-image: url(/images/rewind-icon.svg); -} diff --git a/src/content.config.ts b/src/content.config.ts index 6120d914..0e0c7d44 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,10 +1,10 @@ import { defineCollection } from 'astro:content'; -import { glob } from 'astro/loaders'; +import { transcriptsLoader } from 'starpod/content'; const transcripts = defineCollection({ - loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/transcripts' }), + loader: transcriptsLoader() }); export const collections = { transcripts -}; \ No newline at end of file +}; diff --git a/src/pages/sponsor.astro b/src/pages/sponsor.astro index e64a560c..9586f80f 100644 --- a/src/pages/sponsor.astro +++ b/src/pages/sponsor.astro @@ -1,10 +1,15 @@ --- +// A fully custom page: Starpod injects the core site routes, and anything +// else lives in your own src/pages/, composed from the package's building +// blocks. This one is whiskey.fm's sponsorship pitch and doubles as the +// reference implementation for custom pages. import type { ImageMetadata } from 'astro'; import { Image } from 'astro:assets'; -import Layout from '../layouts/Layout.astro'; -import AdPackageCard from '../components/AdPackageCard.astro'; -import { getShowInfo } from '../lib/rss'; +import AdPackageCard from 'starpod/components/AdPackageCard'; +import Layout from 'starpod/layout'; +import { getShowInfo } from 'starpod/rss'; + const show = await getShowInfo(); const title = `${show.title}`; diff --git a/src/utils/config.ts b/src/utils/config.ts deleted file mode 100644 index c24c53d0..00000000 --- a/src/utils/config.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type Host = { - /** - * The name of the host - */ - name: string; - /** - * A bio about the host to display - */ - bio: string; - /** - * A url to an image to use for the host - */ - img: string; - /** - * A url to the host's GitHub - */ - github?: string; - /** - * A url to the host's Twitter - */ - twitter?: string; - /** - * A url to the host's website - */ - website?: string; -}; - -export type StarpodConfig = { - /** - * A very short tagline for your show. Generally, no more than one sentence. Less is more here. - */ - blurb: string; - /** - * A somewhat longer description of what your show is about. This should still ideally be fairly short, and should usually be 2-4 sentences. - */ - description: string; - /** - * A list of your show's hosts and their info. - */ - hosts: Array; - /** - * Links to the platforms your show is available on. - */ - platforms: { - apple?: string; - appleIdNumber?: string; - overcast?: string; - pocketCasts?: string; - spotify?: string; - youtube?: string; - }; - /** - * The url to the RSS feed where your podcast is hosted. - */ - rssFeed: string; -}; - -export const defineStarpodConfig = (config: StarpodConfig) => config; diff --git a/starpod.config.ts b/starpod.config.ts index 5a38d9ea..7d29a543 100644 --- a/starpod.config.ts +++ b/starpod.config.ts @@ -1,4 +1,4 @@ -import { defineStarpodConfig } from 'src/utils/config'; +import { defineStarpodConfig } from 'starpod/config'; export default defineStarpodConfig({ blurb: @@ -32,5 +32,9 @@ export default defineStarpodConfig({ spotify: 'https://open.spotify.com/show/19jiuHAqzeKnkleQUpZxDf', youtube: 'https://www.youtube.com/@WhiskeyWebAndWhatnot/' }, - rssFeed: 'https://rss.flightcast.com/w7bqgc792i30fd43a32uawx0.xml' + rssFeed: 'https://rss.flightcast.com/w7bqgc792i30fd43a32uawx0.xml', + links: [ + { label: 'Store', href: 'https://whiskey.fund/' }, + { label: 'Become a sponsor', href: '/sponsor' } + ] }); diff --git a/tests/unit/ForwardButton.test.tsx b/tests/unit/ForwardButton.test.tsx index 17591c1e..0a8e7420 100644 --- a/tests/unit/ForwardButton.test.tsx +++ b/tests/unit/ForwardButton.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import ForwardButton from '../../src/components/player/ForwardButton'; +import ForwardButton from 'starpod/src/components/player/ForwardButton'; import { createRef } from 'preact'; // Mock audio element diff --git a/tests/unit/FullPlayButton.test.tsx b/tests/unit/FullPlayButton.test.tsx index d4df6799..30b0e746 100644 --- a/tests/unit/FullPlayButton.test.tsx +++ b/tests/unit/FullPlayButton.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import FullPlayButton from '../../src/components/FullPlayButton'; -import { currentEpisode, isPlaying } from '../../src/components/state'; +import FullPlayButton from 'starpod/src/components/FullPlayButton'; +import { currentEpisode, isPlaying } from 'starpod/src/components/state'; const mockEpisode = { id: 'test-episode-1', diff --git a/tests/unit/MuteButton.test.tsx b/tests/unit/MuteButton.test.tsx index 6453fa4a..c458bd27 100644 --- a/tests/unit/MuteButton.test.tsx +++ b/tests/unit/MuteButton.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import MuteButton from '../../src/components/player/MuteButton'; -import { isMuted } from '../../src/components/state'; +import MuteButton from 'starpod/src/components/player/MuteButton'; +import { isMuted } from 'starpod/src/components/state'; describe('MuteButton', () => { beforeEach(() => { diff --git a/tests/unit/PlayButton.test.tsx b/tests/unit/PlayButton.test.tsx index 81d518ad..f736f2b7 100644 --- a/tests/unit/PlayButton.test.tsx +++ b/tests/unit/PlayButton.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import PlayButton from '../../src/components/player/PlayButton'; -import { currentEpisode, isPlaying } from '../../src/components/state'; +import PlayButton from 'starpod/src/components/player/PlayButton'; +import { currentEpisode, isPlaying } from 'starpod/src/components/state'; const mockEpisode = { id: 'test-episode-1', diff --git a/tests/unit/PlaybackRateButton.test.tsx b/tests/unit/PlaybackRateButton.test.tsx index bc23e7c3..c54cda50 100644 --- a/tests/unit/PlaybackRateButton.test.tsx +++ b/tests/unit/PlaybackRateButton.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import PlaybackRateButton from '../../src/components/player/PlaybackRateButton'; +import PlaybackRateButton from 'starpod/src/components/player/PlaybackRateButton'; import { createRef } from 'preact'; // Mock audio element diff --git a/tests/unit/Player.test.tsx b/tests/unit/Player.test.tsx index 9b822119..17f5fe23 100644 --- a/tests/unit/Player.test.tsx +++ b/tests/unit/Player.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/preact'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import Player from '../../src/components/Player'; -import { currentEpisode, isPlaying, isMuted } from '../../src/components/state'; +import Player from 'starpod/src/components/Player'; +import { currentEpisode, isPlaying, isMuted } from 'starpod/src/components/state'; const mockEpisode = { id: 'test-episode-1', diff --git a/tests/unit/RewindButton.test.tsx b/tests/unit/RewindButton.test.tsx index 8d9ec09d..75559327 100644 --- a/tests/unit/RewindButton.test.tsx +++ b/tests/unit/RewindButton.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import RewindButton from '../../src/components/player/RewindButton'; +import RewindButton from 'starpod/src/components/player/RewindButton'; import { createRef } from 'preact'; // Mock audio element diff --git a/tests/unit/SearchButton.test.tsx b/tests/unit/SearchButton.test.tsx index 8bb6e2e4..1b65e44c 100644 --- a/tests/unit/SearchButton.test.tsx +++ b/tests/unit/SearchButton.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import SearchButton from '../../src/components/SearchButton'; -import { isSearchOpen } from '../../src/components/state'; +import SearchButton from 'starpod/src/components/SearchButton'; +import { isSearchOpen } from 'starpod/src/components/state'; describe('SearchButton', () => { beforeEach(() => { diff --git a/tests/unit/SearchDialog.test.tsx b/tests/unit/SearchDialog.test.tsx index fd4a7950..d3dec7a7 100644 --- a/tests/unit/SearchDialog.test.tsx +++ b/tests/unit/SearchDialog.test.tsx @@ -1,7 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it, vi, afterEach } from 'vitest'; -import SearchDialog from '../../src/components/SearchDialog'; -import { isSearchOpen } from '../../src/components/state'; +import SearchDialog from 'starpod/src/components/SearchDialog'; +import { isSearchOpen } from 'starpod/src/components/state'; // Mock fetch for episodes const mockEpisodes = [ diff --git a/tests/unit/Slider.test.tsx b/tests/unit/Slider.test.tsx index 857c8530..140f7614 100644 --- a/tests/unit/Slider.test.tsx +++ b/tests/unit/Slider.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/preact'; import { beforeEach, describe, expect, it } from 'vitest'; -import Slider from '../../src/components/player/Slider'; +import Slider from 'starpod/src/components/player/Slider'; import { createRef } from 'preact'; // Mock audio element diff --git a/tests/unit/config.test.ts b/tests/unit/config.test.ts new file mode 100644 index 00000000..a31af246 --- /dev/null +++ b/tests/unit/config.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; + +import { + defineStarpodConfig, + validateStarpodConfig, + type StarpodConfig +} from 'starpod/src/utils/config'; + +const validConfig: StarpodConfig = { + blurb: 'A test show.', + description: 'A show for testing Starpod.', + hosts: [{ name: 'Test Host', bio: 'Bio.', img: 'test-host.jpg' }], + platforms: {}, + rssFeed: 'https://example.com/feed.xml' +}; + +describe('defineStarpodConfig', () => { + it('returns a valid config unchanged', () => { + expect(defineStarpodConfig(validConfig)).toEqual(validConfig); + }); + + it('accepts optional links and platform urls', () => { + const config = defineStarpodConfig({ + ...validConfig, + platforms: { spotify: 'https://open.spotify.com/show/abc' }, + links: [{ label: 'Store', href: 'https://example.com/store' }] + }); + expect(config.links).toHaveLength(1); + }); + + it('rejects a non-URL rssFeed and names the field', () => { + expect(() => + validateStarpodConfig({ ...validConfig, rssFeed: 'not a url' }) + ).toThrow(/rssFeed must be a valid URL/); + }); + + it('rejects an empty hosts array', () => { + expect(() => + validateStarpodConfig({ ...validConfig, hosts: [] }) + ).toThrow(/at least one host/); + }); + + it('rejects a missing blurb with the field path', () => { + const { blurb: _blurb, ...withoutBlurb } = validConfig; + expect(() => + validateStarpodConfig(withoutBlurb as StarpodConfig) + ).toThrow(/blurb/); + }); + + it('reports every problem at once', () => { + const broken = { + ...validConfig, + rssFeed: 'nope', + hosts: [] + } as StarpodConfig; + let message = ''; + try { + validateStarpodConfig(broken); + } catch (error) { + message = (error as Error).message; + } + expect(message).toMatch(/rssFeed/); + expect(message).toMatch(/at least one host/); + }); + + it('rejects hosts missing an img with a helpful message', () => { + expect(() => + validateStarpodConfig({ + ...validConfig, + hosts: [{ name: 'X', bio: '', img: '' }] + }) + ).toThrow(/src\/img\/people/); + }); +}); diff --git a/tests/unit/contact-api.test.ts b/tests/unit/contact-api.test.ts index 46879ecd..5da92711 100644 --- a/tests/unit/contact-api.test.ts +++ b/tests/unit/contact-api.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { ALL, POST } from '../../src/pages/api/contact'; +import { ALL, POST } from 'starpod/src/pages/api/contact'; type ApiContext = Parameters[0]; diff --git a/tests/unit/llms.test.ts b/tests/unit/llms.test.ts index de8e476b..44a16ea8 100644 --- a/tests/unit/llms.test.ts +++ b/tests/unit/llms.test.ts @@ -14,9 +14,9 @@ import { generateDeveloperResourcesSection, generateHomeMarkdown, generateContactMarkdown -} from '../../src/lib/llms'; -import type { Episode, Show } from '../../src/lib/rss'; -import type { StarpodConfig } from '../../src/utils/config'; +} from 'starpod/src/lib/llms'; +import type { Episode, Show } from 'starpod/src/lib/rss'; +import type { StarpodConfig } from 'starpod/src/utils/config'; describe('LLM Utilities', () => { describe('formatDuration', () => { diff --git a/tests/unit/not-found.test.ts b/tests/unit/not-found.test.ts index 0655256e..e29746ec 100644 --- a/tests/unit/not-found.test.ts +++ b/tests/unit/not-found.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { generateNotFoundMarkdown } from '../../src/lib/not-found'; -import type { Show } from '../../src/lib/rss'; +import { generateNotFoundMarkdown } from 'starpod/src/lib/not-found'; +import type { Show } from 'starpod/src/lib/rss'; describe('generateNotFoundMarkdown', () => { const mockShow: Show = { diff --git a/tests/unit/openapi.test.ts b/tests/unit/openapi.test.ts index 2a498e06..fd7aed14 100644 --- a/tests/unit/openapi.test.ts +++ b/tests/unit/openapi.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; -import { generateOpenApiSpec } from '../../src/lib/openapi'; -import type { Show } from '../../src/lib/rss'; -import type { StarpodConfig } from '../../src/utils/config'; +import { generateOpenApiSpec } from 'starpod/src/lib/openapi'; +import type { Show } from 'starpod/src/lib/rss'; +import type { StarpodConfig } from 'starpod/src/utils/config'; const mockShow: Show = { title: 'Test Podcast', diff --git a/tests/unit/rehype-transcript-timestamps.test.ts b/tests/unit/rehype-transcript-timestamps.test.ts index edce73ab..8bb9d13a 100644 --- a/tests/unit/rehype-transcript-timestamps.test.ts +++ b/tests/unit/rehype-transcript-timestamps.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import rehypeTranscriptTimestamps from '../../src/lib/rehype-transcript-timestamps.mjs'; +import rehypeTranscriptTimestamps from 'starpod/src/lib/rehype-transcript-timestamps.mjs'; type HastNode = { type: string; diff --git a/tests/unit/robots.test.ts b/tests/unit/robots.test.ts index 9ab1d79d..98ac4aa2 100644 --- a/tests/unit/robots.test.ts +++ b/tests/unit/robots.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, beforeEach } from 'vitest'; -import { getRobotsTxt } from '../../src/pages/robots.txt'; +import { getRobotsTxt } from 'starpod/src/pages/robots.txt'; describe('Robots.txt', () => { let mockSite: URL; diff --git a/tests/unit/transcript.test.ts b/tests/unit/transcript.test.ts index 01bee066..413f767d 100644 --- a/tests/unit/transcript.test.ts +++ b/tests/unit/transcript.test.ts @@ -3,8 +3,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { getRssTranscriptParagraphs, getRssTranscriptText -} from '../../src/lib/transcript'; -import type { Episode } from '../../src/lib/rss'; +} from 'starpod/src/lib/transcript'; +import type { Episode } from 'starpod/src/lib/rss'; // Each test uses a unique transcript URL because the module caches parsed // transcripts by URL β€” reusing a URL would return a previous test's cached diff --git a/tests/unit/vercel-md-negotiation.test.ts b/tests/unit/vercel-md-negotiation.test.ts index b1d7bd51..873ed490 100644 --- a/tests/unit/vercel-md-negotiation.test.ts +++ b/tests/unit/vercel-md-negotiation.test.ts @@ -7,7 +7,7 @@ import { buildNegotiationRoutes, collectMarkdownPaths, patchConfig -} from '../../scripts/vercel-md-negotiation.mjs'; +} from 'starpod/src/vercel-md-negotiation.mjs'; const ACCEPT_MD = [{ type: 'header', key: 'accept', value: '.*text/markdown.*' }]; diff --git a/tsconfig.json b/tsconfig.json index e3696fbb..6bcf9434 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "compilerOptions": { "allowSyntheticDefaultImports": true, "baseUrl": ".", + "paths": { + "starpod/src/*": ["./packages/starpod/src/*"] + }, "esModuleInterop": true, "strictNullChecks": true, "verbatimModuleSyntax": true, @@ -10,5 +13,5 @@ "jsxImportSource": "preact" }, "include": [".astro/types.d.ts", "**/*"], - "exclude": ["dist"] + "exclude": ["dist", "packages/starpod/cli/template"] } diff --git a/vitest.config.ts b/vitest.config.ts index 91f56210..e698fa55 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,3 +1,5 @@ +import { fileURLToPath } from 'node:url'; + import { defineConfig } from 'vitest/config'; export default defineConfig({ @@ -10,7 +12,17 @@ export default defineConfig({ }, resolve: { alias: { - '@': '/src' + '@': '/src', + // Resolve package-internal imports as filesystem paths so + // directory-index imports (e.g. .../player/Slider) work in tests. + 'starpod/src': fileURLToPath( + new URL('./packages/starpod/src', import.meta.url) + ), + // Outside Astro there is no integration to provide the virtual config + // module, so tests resolve it straight to the site's config file. + 'virtual:starpod/config': fileURLToPath( + new URL('./starpod.config.ts', import.meta.url) + ) } } });