From 6ab7fb80240133a318dc7122838f4d424ded4d4c Mon Sep 17 00:00:00 2001 From: Robbie Wagner Date: Sat, 29 Aug 2026 18:04:35 -0400 Subject: [PATCH 1/5] Convert Starpod into an installable Astro integration (#60) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Extract the site engine into a starpod Astro integration package (spike) Monorepo spike: packages/starpod is now an installable Astro integration that injects every route, adds the preact/sitemap/tailwind/rehype wiring, exposes the user's config via virtual:starpod/config, and applies the Vercel markdown content negotiation in astro:build:done. The root site consumes it via workspace:*. Co-Authored-By: Claude Fable 5 * Fix type resolution, hook ordering, and renderer resolution in the integration spike - tsconfig paths + vitest alias so starpod/src/* imports resolve for TS and tests (TS does not resolve the exports wildcard the way Vite does) - Vercel markdown negotiation moves to a process exit handler: the adapter's astro:build:done runs after the integration's and rewrites config.json - @astrojs/preact becomes a direct site dependency: Astro resolves the renderer client entrypoint from the project root, so the consumer must depend on it (matches how framework renderers are normally installed) - Tighten the vercel-md-negotiation type declarations to match its tests Co-Authored-By: Claude Fable 5 * Production-ready integration: hook-time negotiation, opt-in DB, overrides, bundled assets - Vercel markdown negotiation now runs inside astro:build:done using the hook's own assets map (the adapter writes config.json before user hooks but copies static files after them, so the filesystem can't be scanned) - @astrojs/preact and preact become peerDependencies; the package ships a tsconfig.json (nearest-tsconfig JSX settings for installed copies) and widens the preset's JSX exclude so starpod's own .tsx transforms when it lives in node_modules - starpod(config, options): options.database gates the Drizzle/Turso guests+sponsors layer (default off; sections simply don't render), options.components overrides built-ins via virtual modules, and options.customCss loads site stylesheets after the built-in ones - Engine assets (player icons, platform logos, dots, avatars) move into the package and are bundled by Vite instead of expected in consumer public/; host/guest/sponsor image lookups fall back gracefully when a site has not provided photos yet - Normalize rss-to-json's dual CJS interop shape (externalized vs bundled) Co-Authored-By: Claude Fable 5 * Add create-starpod scaffolder npm create starpod: prompts for name/RSS/database (or flags --name --rss --database --yes) and generates a ready-to-build consumer project: renderer deps declared directly, tsconfig with starpod/src/* paths for tests, content config, .env.example, optional db/ folder with empty data files, seed script, and drizzle config, plus the fast-xml-parser pin and sharp that real feeds need. Smoke-tested against the local package with a component override and customCss. Co-Authored-By: Claude Fable 5 * Fold the scaffolder into the starpod package as a CLI Drops the separate create-starpod package: the starpod package now ships a bin, so `npx starpod new my-podcast` scaffolds a project with the same prompts and flags. One published package instead of two; the trade-off is losing the `npm create starpod` alias, which requires a package named create-starpod. Co-Authored-By: Claude Fable 5 * De-Whiskey the engine: custom sponsor pages, config nav links, config-driven host order The sponsor page is no longer part of the engine — sponsorship pitches are bespoke marketing, so a consumer who wants one adds a normal Astro page to their own src/pages/ built from the package's exported Layout and components. whiskey.fm's page moves back into the site as the reference implementation, and the scaffolded README documents the pattern. - New optional `links` config renders extra nav entries after About and Contact (replaces the hardcoded whiskey.fund Store link) - CreatorsAndGuests sorts configured hosts first in config order instead of a hardcoded name list (past hosts recorded in the database follow, then guests alphabetically — old whiskey.fm episodes may order Chuck after Adam now) - AdPackageCard stays in the package as a generic building block Co-Authored-By: Claude Fable 5 * Validate the Starpod config and fail helpfully without one defineStarpodConfig now parses the config with valibot and throws a single readable error listing every problem with its field path; the integration validates too, so configs that skip the helper are still checked. Calling starpod() with no config — what `astro add starpod` generates — throws setup instructions instead of crashing mid-build. Co-Authored-By: Claude Fable 5 * Bless a stable export surface for custom pages starpod/layout, starpod/components/AdPackageCard, and starpod/rss are the supported imports for site-authored pages (the reference sponsor page and scaffolded README now use them). starpod/src/* remains available for tests but is internal API with no stability guarantees. Co-Authored-By: Claude Fable 5 * Document the integration model: package README and repo README rewrite The package README is the npm landing page: quickstart for new and existing projects, config reference, integration options, custom pages, transcripts, database setup, and the API stability policy (src/* is internal; the blessed subpath exports are the stable surface). The repo README keeps its ATProto and LLM discovery docs but replaces the fork-model getting-started with the integration story and documents the workspace layout. Co-Authored-By: Claude Fable 5 * Add CI scaffold smoke test and npm release workflow The scaffold job builds a freshly generated consumer project against the local package on every push — covering the installed-from-npm code path (node_modules JSX transform, CJS interop, bundled assets, dependency pins) that the workspace build can't see, and asserting the agent endpoints and negotiation routes exist in the output. The release workflow publishes starpod to npm with provenance on a starpod@* tag or manual dispatch; it needs an NPM_TOKEN repository secret. Co-Authored-By: Claude Fable 5 * Publish via npm OIDC trusted publishing instead of a token secret npm handles the OIDC exchange natively (id-token: write stays; the workflow upgrades npm since Node 22 bundles 10.x and trusted publishing needs 11.5.1+), and provenance comes automatically with trusted-publisher releases. Requires the one-time trusted publisher setup on npmjs.com. Co-Authored-By: Claude Fable 5 * Set up release-plan for automated releases Generated by create-release-plan-setup: plan-release.yml opens and updates a Prepare Release PR (changelog + .release-plan.json) from labeled PRs, and publish.yml releases to npm via OIDC trusted publishing with provenance when that PR merges — no token secret. Replaces the hand-rolled tag-based release workflow. RELEASE.md documents the flow; every merged PR needs one of the changelog labels. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- .github/workflows/ci.yml | 28 + .github/workflows/plan-release.yml | 61 + .github/workflows/publish.yml | 40 + CHANGELOG.md | 1 + README.md | 92 +- RELEASE.md | 27 + astro.config.mjs | 65 +- db/seed.ts | 9 +- drizzle.config.ts | 2 +- package.json | 36 +- packages/starpod/.impeccable/hook.cache.json | 1 + packages/starpod/README.md | 187 ++ packages/starpod/cli/index.mjs | 261 +++ packages/starpod/cli/template/base/README.md | 68 + .../starpod/cli/template/base/env.example | 7 + packages/starpod/cli/template/base/gitignore | 20 + .../starpod/cli/template/base/public/.gitkeep | 0 .../cli/template/base/src/content.config.ts | 10 + .../base/src/content/transcripts/.gitkeep | 0 .../cli/template/base/src/img/people/.gitkeep | 0 .../starpod/cli/template/base/tsconfig.json | 17 + .../database/db/data/people-per-episode.ts | 12 + .../cli/template/database/db/data/people.ts | 9 + .../database/db/data/sponsors-per-episode.ts | 6 + .../cli/template/database/db/data/sponsors.ts | 7 + .../starpod/cli/template/database/db/seed.ts | 99 ++ .../cli/template/database/drizzle.config.ts | 12 + packages/starpod/package.json | 66 + .../starpod/src/assets}/apple.svg | 0 .../starpod/src/assets}/avatar-dark.png | Bin .../starpod/src/assets}/avatar-light.png | Bin .../starpod/src/assets}/dots-dark.svg | 0 .../starpod/src/assets}/dots-light.svg | 0 .../starpod/src/assets}/forward-icon.svg | 0 .../starpod/src/assets}/mute-icon.svg | 0 .../starpod/src/assets}/overcast.svg | 0 .../starpod/src/assets}/pocket-casts.svg | 0 .../starpod/src/assets}/rewind-icon.svg | 0 .../starpod/src/assets}/rocket-dark.svg | 0 .../starpod/src/assets}/rocket-light.svg | 0 .../starpod/src/assets}/search-icon.svg | 0 .../starpod/src/assets}/spotify.svg | 0 .../starpod/src/assets}/unmute-icon.svg | 0 .../starpod/src/assets}/youtube.svg | 0 .../src}/components/AdPackageCard.astro | 0 .../starpod/src}/components/Breadcrumbs.astro | 0 .../starpod/src}/components/ContactForm.tsx | 0 .../starpod/src}/components/Dots.astro | 0 .../starpod/src}/components/EpisodeList.astro | 0 .../starpod/src}/components/FormattedDate.tsx | 0 .../src}/components/FullPlayButton.tsx | 0 .../starpod/src}/components/Hosts.astro | 11 +- .../starpod/src}/components/InfoCard.astro | 22 +- .../src}/components/LargePlatforms.astro | 2 +- .../src}/components/NotFoundContent.astro | 0 .../starpod/src}/components/Platforms.astro | 12 +- .../starpod/src}/components/Player.tsx | 0 .../starpod/src}/components/SearchButton.tsx | 0 .../starpod/src}/components/SearchDialog.tsx | 0 .../starpod/src}/components/ShowArtwork.astro | 0 .../episode/CreatorsAndGuests.astro | 15 +- .../components/episode/MarkdownTranscript.tsx | 0 .../src}/components/episode/Sponsors.astro | 4 +- .../src}/components/episode/Transcript.tsx | 0 .../illustrations/404Illustration.astro | 0 .../illustrations/UFOIllustration.astro | 0 .../components/player/ForwardButton/index.tsx | 0 .../player/ForwardButton/styles.css | 3 + .../components/player/MuteButton/index.tsx | 0 .../components/player/MuteButton/styles.css | 7 + .../src}/components/player/PlayButton.tsx | 0 .../components/player/PlaybackRateButton.tsx | 0 .../components/player/RewindButton/index.tsx | 0 .../components/player/RewindButton/styles.css | 3 + .../src}/components/player/Slider/index.tsx | 0 .../src}/components/player/Slider/styles.css | 8 +- .../starpod/src}/components/state.ts | 0 packages/starpod/src/config.ts | 7 + packages/starpod/src/content.ts | 18 + {db => packages/starpod/src/db}/index.ts | 0 {db => packages/starpod/src/db}/schema.ts | 0 packages/starpod/src/index.ts | 285 +++ .../starpod/src}/layouts/Layout.astro | 15 +- .../starpod/src}/lib/api-errors.ts | 0 {src => packages/starpod/src}/lib/llms.ts | 0 .../starpod/src}/lib/not-found.ts | 0 {src => packages/starpod/src}/lib/openapi.ts | 0 .../src}/lib/optimize-episode-image.ts | 0 .../lib/rehype-transcript-timestamps.d.mts | 4 + .../src}/lib/rehype-transcript-timestamps.mjs | 0 {src => packages/starpod/src}/lib/rss.ts | 35 +- .../starpod/src}/lib/standardSite.ts | 0 .../starpod/src}/lib/transcript.ts | 0 .../.well-known/site.standard.publication.ts | 0 {src => packages/starpod/src}/pages/404.astro | 2 +- .../starpod/src}/pages/[...notFound].astro | 2 +- .../starpod/src}/pages/[episode].astro | 76 +- .../starpod/src}/pages/[episode].html.md.ts | 2 +- .../starpod/src}/pages/about.astro | 13 +- .../starpod/src}/pages/about.html.md.ts | 2 +- .../starpod/src}/pages/api/contact.ts | 0 .../src}/pages/api/episodes/[page].json.ts | 0 .../src}/pages/api/episodes/search.json.ts | 0 .../starpod/src}/pages/contact.astro | 0 .../starpod/src}/pages/contact.html.md.ts | 2 +- .../src}/pages/episodes-index.html.md.ts | 0 .../starpod/src}/pages/for-llms.astro | 2 +- .../starpod/src}/pages/for-llms.html.md.ts | 2 +- .../starpod/src}/pages/index.astro | 2 +- .../starpod/src}/pages/index.html.md.ts | 2 +- .../starpod/src}/pages/llms.txt.ts | 2 +- .../starpod/src}/pages/openapi.json.ts | 2 +- .../starpod/src}/pages/robots.txt.ts | 0 .../starpod/src}/styles/buttons.css | 0 .../starpod/src}/styles/global.css | 0 .../starpod/src}/styles/gradient-icon.css | 0 .../starpod/src}/styles/search-icon.css | 2 +- .../starpod/src}/styles/tailwind.css | 13 +- .../starpod/src}/svgs/404-dark.svg | 0 .../starpod/src}/svgs/404-light.svg | 0 {src => packages/starpod/src}/svgs/apple.svg | 0 .../starpod/src}/svgs/overcast.svg | 0 .../starpod/src}/svgs/pocket-casts.svg | 0 .../starpod/src}/svgs/spotify.svg | 0 .../starpod/src}/svgs/ufo-dark.svg | 0 .../starpod/src}/svgs/ufo-light.svg | 0 .../starpod/src}/svgs/youtube.svg | 0 packages/starpod/src/utils/config.ts | 161 ++ .../starpod/src}/utils/dasherize.ts | 0 .../starpod/src}/utils/truncate.ts | 0 .../starpod/src/vercel-md-negotiation.d.mts | 27 + .../starpod/src}/vercel-md-negotiation.mjs | 22 +- packages/starpod/src/virtual.d.ts | 16 + packages/starpod/tsconfig.json | 12 + playwright.worktree.config.ts | 23 + pnpm-lock.yaml | 1543 ++++++++++++++++- pnpm-workspace.yaml | 2 + scripts/publish-atproto-episodes.ts | 2 +- .../player/ForwardButton/styles.css | 3 - src/components/player/MuteButton/styles.css | 7 - src/components/player/RewindButton/styles.css | 3 - src/content.config.ts | 6 +- src/pages/sponsor.astro | 11 +- src/utils/config.ts | 58 - starpod.config.ts | 8 +- tests/unit/ForwardButton.test.tsx | 2 +- tests/unit/FullPlayButton.test.tsx | 4 +- tests/unit/MuteButton.test.tsx | 4 +- tests/unit/PlayButton.test.tsx | 4 +- tests/unit/PlaybackRateButton.test.tsx | 2 +- tests/unit/Player.test.tsx | 4 +- tests/unit/RewindButton.test.tsx | 2 +- tests/unit/SearchButton.test.tsx | 4 +- tests/unit/SearchDialog.test.tsx | 4 +- tests/unit/Slider.test.tsx | 2 +- tests/unit/config.test.ts | 74 + tests/unit/contact-api.test.ts | 2 +- tests/unit/llms.test.ts | 6 +- tests/unit/not-found.test.ts | 4 +- tests/unit/openapi.test.ts | 6 +- .../unit/rehype-transcript-timestamps.test.ts | 2 +- tests/unit/robots.test.ts | 2 +- tests/unit/transcript.test.ts | 4 +- tests/unit/vercel-md-negotiation.test.ts | 2 +- tsconfig.json | 5 +- vitest.config.ts | 14 +- 166 files changed, 3414 insertions(+), 363 deletions(-) create mode 100644 .github/workflows/plan-release.yml create mode 100644 .github/workflows/publish.yml create mode 100644 CHANGELOG.md create mode 100644 RELEASE.md create mode 100644 packages/starpod/.impeccable/hook.cache.json create mode 100644 packages/starpod/README.md create mode 100755 packages/starpod/cli/index.mjs create mode 100644 packages/starpod/cli/template/base/README.md create mode 100644 packages/starpod/cli/template/base/env.example create mode 100644 packages/starpod/cli/template/base/gitignore rename src/content/config.ts => packages/starpod/cli/template/base/public/.gitkeep (100%) create mode 100644 packages/starpod/cli/template/base/src/content.config.ts create mode 100644 packages/starpod/cli/template/base/src/content/transcripts/.gitkeep create mode 100644 packages/starpod/cli/template/base/src/img/people/.gitkeep create mode 100644 packages/starpod/cli/template/base/tsconfig.json create mode 100644 packages/starpod/cli/template/database/db/data/people-per-episode.ts create mode 100644 packages/starpod/cli/template/database/db/data/people.ts create mode 100644 packages/starpod/cli/template/database/db/data/sponsors-per-episode.ts create mode 100644 packages/starpod/cli/template/database/db/data/sponsors.ts create mode 100644 packages/starpod/cli/template/database/db/seed.ts create mode 100644 packages/starpod/cli/template/database/drizzle.config.ts create mode 100644 packages/starpod/package.json rename {public/images => packages/starpod/src/assets}/apple.svg (100%) rename {src/img/people => packages/starpod/src/assets}/avatar-dark.png (100%) rename {src/img/people => packages/starpod/src/assets}/avatar-light.png (100%) rename {public/images => packages/starpod/src/assets}/dots-dark.svg (100%) rename {public/images => packages/starpod/src/assets}/dots-light.svg (100%) rename {public/images => packages/starpod/src/assets}/forward-icon.svg (100%) rename {public/images => packages/starpod/src/assets}/mute-icon.svg (100%) rename {public/images => packages/starpod/src/assets}/overcast.svg (100%) rename {public/images => packages/starpod/src/assets}/pocket-casts.svg (100%) rename {public/images => packages/starpod/src/assets}/rewind-icon.svg (100%) rename {public/images => packages/starpod/src/assets}/rocket-dark.svg (100%) rename {public/images => packages/starpod/src/assets}/rocket-light.svg (100%) rename {public/images => packages/starpod/src/assets}/search-icon.svg (100%) rename {public/images => packages/starpod/src/assets}/spotify.svg (100%) rename {public/images => packages/starpod/src/assets}/unmute-icon.svg (100%) rename {public/images => packages/starpod/src/assets}/youtube.svg (100%) rename {src => packages/starpod/src}/components/AdPackageCard.astro (100%) rename {src => packages/starpod/src}/components/Breadcrumbs.astro (100%) rename {src => packages/starpod/src}/components/ContactForm.tsx (100%) rename {src => packages/starpod/src}/components/Dots.astro (100%) rename {src => packages/starpod/src}/components/EpisodeList.astro (100%) rename {src => packages/starpod/src}/components/FormattedDate.tsx (100%) rename {src => packages/starpod/src}/components/FullPlayButton.tsx (100%) rename {src => packages/starpod/src}/components/Hosts.astro (67%) rename {src => packages/starpod/src}/components/InfoCard.astro (53%) rename {src => packages/starpod/src}/components/LargePlatforms.astro (98%) rename {src => packages/starpod/src}/components/NotFoundContent.astro (100%) rename {src => packages/starpod/src}/components/Platforms.astro (83%) rename {src => packages/starpod/src}/components/Player.tsx (100%) rename {src => packages/starpod/src}/components/SearchButton.tsx (100%) rename {src => packages/starpod/src}/components/SearchDialog.tsx (100%) rename {src => packages/starpod/src}/components/ShowArtwork.astro (100%) rename {src => packages/starpod/src}/components/episode/CreatorsAndGuests.astro (80%) rename {src => packages/starpod/src}/components/episode/MarkdownTranscript.tsx (100%) rename {src => packages/starpod/src}/components/episode/Sponsors.astro (85%) rename {src => packages/starpod/src}/components/episode/Transcript.tsx (100%) rename {src => packages/starpod/src}/components/illustrations/404Illustration.astro (100%) rename {src => packages/starpod/src}/components/illustrations/UFOIllustration.astro (100%) rename {src => packages/starpod/src}/components/player/ForwardButton/index.tsx (100%) create mode 100644 packages/starpod/src/components/player/ForwardButton/styles.css rename {src => packages/starpod/src}/components/player/MuteButton/index.tsx (100%) create mode 100644 packages/starpod/src/components/player/MuteButton/styles.css rename {src => packages/starpod/src}/components/player/PlayButton.tsx (100%) rename {src => packages/starpod/src}/components/player/PlaybackRateButton.tsx (100%) rename {src => packages/starpod/src}/components/player/RewindButton/index.tsx (100%) create mode 100644 packages/starpod/src/components/player/RewindButton/styles.css rename {src => packages/starpod/src}/components/player/Slider/index.tsx (100%) rename {src => packages/starpod/src}/components/player/Slider/styles.css (91%) rename {src => packages/starpod/src}/components/state.ts (100%) create mode 100644 packages/starpod/src/config.ts create mode 100644 packages/starpod/src/content.ts rename {db => packages/starpod/src/db}/index.ts (100%) rename {db => packages/starpod/src/db}/schema.ts (100%) create mode 100644 packages/starpod/src/index.ts rename {src => packages/starpod/src}/layouts/Layout.astro (92%) rename {src => packages/starpod/src}/lib/api-errors.ts (100%) rename {src => packages/starpod/src}/lib/llms.ts (100%) rename {src => packages/starpod/src}/lib/not-found.ts (100%) rename {src => packages/starpod/src}/lib/openapi.ts (100%) rename {src => packages/starpod/src}/lib/optimize-episode-image.ts (100%) create mode 100644 packages/starpod/src/lib/rehype-transcript-timestamps.d.mts rename {src => packages/starpod/src}/lib/rehype-transcript-timestamps.mjs (100%) rename {src => packages/starpod/src}/lib/rss.ts (77%) rename {src => packages/starpod/src}/lib/standardSite.ts (100%) rename {src => packages/starpod/src}/lib/transcript.ts (100%) rename {src => packages/starpod/src}/pages/.well-known/site.standard.publication.ts (100%) rename {src => packages/starpod/src}/pages/404.astro (61%) rename {src => packages/starpod/src}/pages/[...notFound].astro (94%) rename {src => packages/starpod/src}/pages/[episode].astro (87%) rename {src => packages/starpod/src}/pages/[episode].html.md.ts (96%) rename {src => packages/starpod/src}/pages/about.astro (84%) rename {src => packages/starpod/src}/pages/about.html.md.ts (88%) rename {src => packages/starpod/src}/pages/api/contact.ts (100%) rename {src => packages/starpod/src}/pages/api/episodes/[page].json.ts (100%) rename {src => packages/starpod/src}/pages/api/episodes/search.json.ts (100%) rename {src => packages/starpod/src}/pages/contact.astro (100%) rename {src => packages/starpod/src}/pages/contact.html.md.ts (88%) rename {src => packages/starpod/src}/pages/episodes-index.html.md.ts (100%) rename {src => packages/starpod/src}/pages/for-llms.astro (99%) rename {src => packages/starpod/src}/pages/for-llms.html.md.ts (90%) rename {src => packages/starpod/src}/pages/index.astro (87%) rename {src => packages/starpod/src}/pages/index.html.md.ts (90%) rename {src => packages/starpod/src}/pages/llms.txt.ts (92%) rename {src => packages/starpod/src}/pages/openapi.json.ts (89%) rename {src => packages/starpod/src}/pages/robots.txt.ts (100%) rename {src => packages/starpod/src}/styles/buttons.css (100%) rename {src => packages/starpod/src}/styles/global.css (100%) rename {src => packages/starpod/src}/styles/gradient-icon.css (100%) rename {src => packages/starpod/src}/styles/search-icon.css (72%) rename {src => packages/starpod/src}/styles/tailwind.css (82%) rename {src => packages/starpod/src}/svgs/404-dark.svg (100%) rename {src => packages/starpod/src}/svgs/404-light.svg (100%) rename {src => packages/starpod/src}/svgs/apple.svg (100%) rename {src => packages/starpod/src}/svgs/overcast.svg (100%) rename {src => packages/starpod/src}/svgs/pocket-casts.svg (100%) rename {src => packages/starpod/src}/svgs/spotify.svg (100%) rename {src => packages/starpod/src}/svgs/ufo-dark.svg (100%) rename {src => packages/starpod/src}/svgs/ufo-light.svg (100%) rename {src => packages/starpod/src}/svgs/youtube.svg (100%) create mode 100644 packages/starpod/src/utils/config.ts rename {src => packages/starpod/src}/utils/dasherize.ts (100%) rename {src => packages/starpod/src}/utils/truncate.ts (100%) create mode 100644 packages/starpod/src/vercel-md-negotiation.d.mts rename {scripts => packages/starpod/src}/vercel-md-negotiation.mjs (89%) create mode 100644 packages/starpod/src/virtual.d.ts create mode 100644 packages/starpod/tsconfig.json create mode 100644 playwright.worktree.config.ts create mode 100644 pnpm-workspace.yaml delete mode 100644 src/components/player/ForwardButton/styles.css delete mode 100644 src/components/player/MuteButton/styles.css delete mode 100644 src/components/player/RewindButton/styles.css delete mode 100644 src/utils/config.ts create mode 100644 tests/unit/config.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dea1dd5..002838c 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 0000000..b2843ad --- /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 0000000..77725c0 --- /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 0000000..825c32f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/README.md b/README.md index 8c51e0d..8c8a7e2 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 0000000..4b75c8d --- /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 cc4fb4f..9ffe840 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 68c4f53..d0d8016 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 4361179..f1e8d18 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 4e9de4f..6258e47 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 0000000..a7383d1 --- /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 0000000..1dc8426 --- /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 0000000..e8306a2 --- /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 0000000..9e686ac --- /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 0000000..96a3fc3 --- /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 0000000..bfc093c --- /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 0000000..0e0c7d4 --- /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 0000000..e69de29 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 0000000..e69de29 diff --git a/packages/starpod/cli/template/base/tsconfig.json b/packages/starpod/cli/template/base/tsconfig.json new file mode 100644 index 0000000..019422a --- /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 0000000..b9d45ab --- /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 0000000..a1e8b17 --- /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 0000000..ce0c8c7 --- /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 0000000..84670ed --- /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 0000000..51a380c --- /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 0000000..43eeee7 --- /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 0000000..f257822 --- /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 dbd68b9..fe64fce 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 631b10f..d12242e 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 972e429..47c8b7e 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 358b600..8e91149 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 66f3a40..7a80416 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 0000000..e823e2d --- /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 0000000..9c85f9a --- /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 0000000..a795fa4 --- /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 790defe..33ec5ca 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 0000000..528530c --- /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 0000000..2673550 --- /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 0000000..8f4f777 --- /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 fd7057e..ba36cc0 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 0000000..ebf294a --- /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 e562e61..91670ce 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 1cadd86..49fb428 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 0170f24..335c54d 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 c00087e..e70d339 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 bab5450..0413bef 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 729658e..32356e1 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 0d51e17..0e9b3fb 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 ffe564f..c4372ce 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 f825d6c..09527a5 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 a9fedf2..eeef10c 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 6a4f648..5757719 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 24c8856..c6b33ae 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 9b6767e..39e1125 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 bf4d506..34b1e8b 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 33a130b..b1e8f09 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 fdd85d8..dcb2656 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 0000000..3a4ba68 --- /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 0000000..bc6112d --- /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 ca18f9b..cf3bac4 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 0000000..0101873 --- /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 0000000..4d3cf63 --- /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 0000000..d01db43 --- /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 389b06c..b660fd0 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 0000000..924b55f --- /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 4010972..c6f47e3 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 e220fe0..0000000 --- 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 c78a636..0000000 --- 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 c379441..0000000 --- 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 6120d91..0e0c7d4 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 e64a560..9586f80 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 c24c53d..0000000 --- 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 5a38d9e..7d29a54 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 17591c1..0a8e742 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 d4df679..30b0e74 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 6453fa4..c458bd2 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 81d518a..f736f2b 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 bc23e7c..c54cda5 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 9b82211..17f5fe2 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 8d9ec09..7555932 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 8bb6e2e..1b65e44 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 fd4a795..d3dec7a 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 857c853..140f761 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 0000000..a31af24 --- /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 46879ec..5da9271 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 de8e476..44a16ea 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 0655256..e29746e 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 2a498e0..fd7aed1 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 edce73a..8bb9d13 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 9ab1d79..98ac4aa 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 01bee06..413f767 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 b1d7bd5..873ed49 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 e3696fb..6bcf943 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 91f5621..e698fa5 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) + ) } } }); From 3d9a168b783f9f211f4fda8e63d75e095eef054b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:32:54 -0400 Subject: [PATCH 2/5] Prepare Release v1.0.0 using 'release-plan' (#61) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .release-plan.json | 18 ++++++++++++++++++ CHANGELOG.md | 11 +++++++++++ packages/starpod/package.json | 8 ++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .release-plan.json diff --git a/.release-plan.json b/.release-plan.json new file mode 100644 index 0000000..b14999d --- /dev/null +++ b/.release-plan.json @@ -0,0 +1,18 @@ +{ + "solution": { + "starpod": { + "impact": "major", + "oldVersion": "0.1.0", + "newVersion": "1.0.0", + "tagName": "latest", + "constraints": [ + { + "impact": "major", + "reason": "Appears in changelog section :boom: Breaking Change" + } + ], + "pkgJSONPath": "./packages/starpod/package.json" + } + }, + "description": "## Release (2026-08-29)\n\n* starpod 1.0.0 (major)\n\n#### :boom: Breaking Change\n* `starpod`\n * [#60](https://github.com/shipshapecode/starpod/pull/60) Convert Starpod into an installable Astro integration ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n\n#### Committers: 1\n- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 825c32f..ff70431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,12 @@ # Changelog + +## Release (2026-08-29) + +* starpod 1.0.0 (major) + +#### :boom: Breaking Change +* `starpod` + * [#60](https://github.com/shipshapecode/starpod/pull/60) Convert Starpod into an installable Astro integration ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) + +#### Committers: 1 +- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) diff --git a/packages/starpod/package.json b/packages/starpod/package.json index f257822..14bc9c7 100644 --- a/packages/starpod/package.json +++ b/packages/starpod/package.json @@ -1,7 +1,7 @@ { "name": "starpod", "type": "module", - "version": "0.1.0", + "version": "1.0.0", "description": "Turn an RSS feed into a full podcast website. An Astro integration.", "repository": { "type": "git", @@ -18,7 +18,11 @@ "bin": { "starpod": "./cli/index.mjs" }, - "files": ["cli", "src", "tsconfig.json"], + "files": [ + "cli", + "src", + "tsconfig.json" + ], "exports": { ".": "./src/index.ts", "./config": "./src/config.ts", From 0d835bea5f872c38eb29c86aeeed1a001e231ee1 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Sat, 29 Aug 2026 20:12:37 -0400 Subject: [PATCH 3/5] Fix Preact JSX transform for starpod sources under node_modules in Vitest The workspace repo resolves starpod's .tsx files outside node_modules where the tsconfig JSX settings apply; installed from npm they resolve inside node_modules, so the transform must be configured explicitly. Co-Authored-By: Claude Fable 5 --- pnpm-lock.yaml | 117 ++++++++++++++++++++++++++++++++--------------- vitest.config.ts | 7 +++ 2 files changed, 88 insertions(+), 36 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6fc97c..b8d3130 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,42 +20,27 @@ importers: '@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 '@polar-sh/astro': specifier: ^0.5.0 version: 0.5.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)) - '@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: ^1.0.0 + version: 1.0.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)) valibot: specifier: ^1.4.1 version: 1.4.1(typescript@5.9.3) @@ -63,24 +48,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 @@ -114,12 +87,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 @@ -3852,6 +3825,15 @@ packages: standardwebhooks@1.0.0: resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + starpod@1.0.0: + resolution: {integrity: sha512-5f12+0q3dN2sc3yQjA54hn5oXnQbRUflXnnYMjl37ufISCiww36fFagm3cYxI9DxNEpGfgf5L+l5dggGWhKwhA==} + engines: {node: ^22.0.0} + hasBin: true + peerDependencies: + '@astrojs/preact': ^5.0.0 + astro: ^6.0.0 + preact: ^10.0.0 + std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} @@ -8334,6 +8316,69 @@ snapshots: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 + starpod@1.0.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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: + '@astrojs/preact': 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/sitemap': 3.7.3 + '@bryanguffey/astro-standard-site': 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': 0.17.3 + '@preact/signals': 2.9.1(preact@10.29.2) + '@tailwindcss/forms': 0.5.11(tailwindcss@4.3.0) + '@tailwindcss/typography': 0.5.19(tailwindcss@4.3.0) + '@tailwindcss/vite': 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': 1.3.1(react@19.0.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) + astro-seo-schema: 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: 2.0.2 + drizzle-orm: 0.45.2(@libsql/client@0.17.3) + html-to-text: 9.0.5 + preact: 10.29.2 + rss-to-json: 2.1.1 + schema-dts: 1.1.5 + tailwindcss: 4.3.0 + valibot: 1.4.1(typescript@5.9.3) + transitivePeerDependencies: + - '@aws-sdk/client-rds-data' + - '@cloudflare/workers-types' + - '@electric-sql/pglite' + - '@libsql/client-wasm' + - '@neondatabase/serverless' + - '@op-engineering/op-sqlite' + - '@opentelemetry/api' + - '@planetscale/database' + - '@prisma/client' + - '@sveltejs/kit' + - '@tidbcloud/serverless' + - '@types/better-sqlite3' + - '@types/pg' + - '@types/sql.js' + - '@upstash/redis' + - '@vercel/postgres' + - '@xata.io/client' + - better-sqlite3 + - bufferutil + - bun-types + - debug + - expo-sqlite + - gel + - knex + - kysely + - mysql2 + - next + - pg + - postgres + - prisma + - react + - sql.js + - sqlite3 + - supports-color + - svelte + - typescript + - utf-8-validate + - vite + - vue + - vue-router + std-env@4.1.0: {} stop-iteration-iterator@1.1.0: diff --git a/vitest.config.ts b/vitest.config.ts index 460a272..fb2865e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,6 +3,13 @@ import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; export default defineConfig({ + // starpod ships .tsx sources that resolve inside node_modules, where the + // tsconfig-based JSX detection doesn't apply — configure the Preact JSX + // transform explicitly. + esbuild: { + jsx: 'automatic', + jsxImportSource: 'preact' + }, test: { environment: 'jsdom', setupFiles: ['./tests/unit/test-setup.ts'], From ea2bcbaf0e09ccdee491ed8b043fd12ad799d2ef Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Sat, 29 Aug 2026 20:47:39 -0400 Subject: [PATCH 4/5] Bump starpod to 1.0.1 for site-wide site.standard.publication tag starpod@1.0.1's Layout restores the site-wide tag (gated on STANDARD_SITE_DID and STANDARD_SITE_PUBLICATION_RKEY) that the forked Layout emitted before converting to the npm package. Co-Authored-By: Claude Fable 5 --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cab68bb..b306805 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "drizzle-orm": "^0.45.2", "html-to-text": "^9.0.5", "rss-to-json": "^2.1.1", - "starpod": "^1.0.0", + "starpod": "^1.0.1", "valibot": "^1.4.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8d3130..106a5ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 starpod: - specifier: ^1.0.0 - version: 1.0.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)) + specifier: ^1.0.1 + version: 1.0.1(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)) valibot: specifier: ^1.4.1 version: 1.4.1(typescript@5.9.3) @@ -3825,8 +3825,8 @@ packages: standardwebhooks@1.0.0: resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} - starpod@1.0.0: - resolution: {integrity: sha512-5f12+0q3dN2sc3yQjA54hn5oXnQbRUflXnnYMjl37ufISCiww36fFagm3cYxI9DxNEpGfgf5L+l5dggGWhKwhA==} + starpod@1.0.1: + resolution: {integrity: sha512-oxEXl23+yIiFww/xnFbJMBTWR9P+hJwW8E4kMoukPG9IoFx787oALo0aHgQ2OZbaTY+Wvvkfn6kTy3EqG2auhg==} engines: {node: ^22.0.0} hasBin: true peerDependencies: @@ -8316,7 +8316,7 @@ snapshots: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 - starpod@1.0.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)): + starpod@1.0.1(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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: '@astrojs/preact': 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/sitemap': 3.7.3 From 3e368a6fd4bdafc8f4cb25d4dd3c9b03a7f4cbad Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Wed, 2 Sep 2026 14:45:45 -0400 Subject: [PATCH 5/5] Bump starpod to 1.1.0 for generalized breadcrumbs Collection detail pages get their linked Collections middle crumb back (with aria-current and the full BreadcrumbList schema), and the legacy mask-icon/msapplication-TileColor tags are gone upstream, closing the last parity gaps from the fork conversion. Co-Authored-By: Claude Fable 5 --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b306805..40505f5 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "drizzle-orm": "^0.45.2", "html-to-text": "^9.0.5", "rss-to-json": "^2.1.1", - "starpod": "^1.0.1", + "starpod": "^1.1.0", "valibot": "^1.4.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 106a5ad..865b15f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: ^2.1.1 version: 2.1.1 starpod: - specifier: ^1.0.1 - version: 1.0.1(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)) + specifier: ^1.1.0 + version: 1.1.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)) valibot: specifier: ^1.4.1 version: 1.4.1(typescript@5.9.3) @@ -3825,8 +3825,8 @@ packages: standardwebhooks@1.0.0: resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} - starpod@1.0.1: - resolution: {integrity: sha512-oxEXl23+yIiFww/xnFbJMBTWR9P+hJwW8E4kMoukPG9IoFx787oALo0aHgQ2OZbaTY+Wvvkfn6kTy3EqG2auhg==} + starpod@1.1.0: + resolution: {integrity: sha512-o1khSIO8RpqPnJcAed2rNpW46hHhJ7OlwHIPtHvSe3pzbdP1/JydOu/X8Y+C5MbySGs9Pf0mmiMxdVm5pJoL8w==} engines: {node: ^22.0.0} hasBin: true peerDependencies: @@ -8316,7 +8316,7 @@ snapshots: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 - starpod@1.0.1(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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)): + starpod@1.1.0(@astrojs/preact@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))(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))(preact@10.29.2)(react@19.0.0)(typescript@5.9.3)(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: '@astrojs/preact': 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/sitemap': 3.7.3