Skip to content

Convert Starpod into an installable Astro integration - #60

Merged
RobbieTheWagner merged 13 commits into
mainfrom
astro-integration
Aug 29, 2026
Merged

Convert Starpod into an installable Astro integration#60
RobbieTheWagner merged 13 commits into
mainfrom
astro-integration

Conversation

@RobbieTheWagner

Copy link
Copy Markdown
Member

What this is

Starpod becomes an npm package instead of a fork-me template. packages/starpod is an Astro integration that generates the entire site — episode pages, player, search, transcripts, the LLM/agent endpoints, and the Vercel markdown content negotiation — from an RSS feed and a starpod.config.ts. The repo root is now whiskey.fm, the reference site, consuming the package via workspace:*. Sites built on the package update with pnpm update instead of merging a diverged fork.

Consumer experience

  • New projects: npx starpod new my-podcast scaffolds a ready-to-deploy site (the CLI ships inside the starpod package — one package on npm)
  • Existing Astro projects: pnpm add starpod @astrojs/preact preact, create starpod.config.ts, add starpod(starpodConfig) to integrations; astro add starpod users get exact setup instructions instead of a crash
  • Config is validated with valibot — one readable error listing every problem with its field path
  • Options: database (opt-in Turso/Drizzle guests & sponsors; off = those sections just don't render), components (Starlight-style overrides for 8 built-ins), customCss (theming via CSS custom properties)
  • Custom pages are plain Astro pages composed from blessed exports (starpod/layout, starpod/components/AdPackageCard, starpod/rss) — whiskey.fm's sponsor page is the reference implementation
  • starpod/src/* stays importable for tests but is documented as internal API

Notable engineering

  • Config flows through a virtual:starpod/config module; lib functions accept an optional config so tsx scripts (seed, ATProto publishing) work without Vite
  • Markdown negotiation runs inside astro:build:done using the hook's assets map (the adapter writes config.json before user hooks but copies static files after them)
  • Engine assets (icons, dots, avatars) are bundled by Vite instead of expected in consumer public/; missing host/guest/sponsor images fall back gracefully
  • Fixes that only reproduce when installed from npm, caught by scaffold smoke testing: nearest-tsconfig JSX for node_modules sources, the preact preset's node_modules JSX exclusion, rss-to-json's dual CJS interop shape, the fast-xml-parser entity limit, missing sharp
  • CI gains a scaffold job that builds a freshly generated consumer against the local package on every push
  • Releases via release-plan with npm OIDC trusted publishing (no token secret)

Verification

astro check 0 errors · 170 unit tests · 51 e2e tests (chromium/firefox/webkit) · full Vercel build with all negotiation routes · scaffolded consumer builds end-to-end with database off and a component override + customCss exercised

Before first release (manual, one-time)

  1. Configure the npm trusted publisher for starpod (GitHub Actions, repo shipshapecode/starpod, workflow publish.yml)
  2. Push a baseline tag (e.g. v0.0.0) — the changelog generator needs one to diff from
  3. Keep every merged PR labeled (this one is enhancement)

🤖 Generated with Claude Code

RobbieTheWagner and others added 13 commits August 24, 2026 10:45
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 <noreply@anthropic.com>
…tegration 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 <noreply@anthropic.com>
…ides, 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
# Conflicts:
#	packages/starpod/src/pages/contact.html.md.ts
#	packages/starpod/src/pages/index.html.md.ts
#	tests/unit/llms.test.ts
…-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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
@RobbieTheWagner RobbieTheWagner added the enhancement New feature or request label Aug 29, 2026
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
starpod Ready Ready Preview Aug 29, 2026 9:41pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 140 files, which is 40 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f430bf00-df8f-46fc-a48c-b0be59bfcaf4

📥 Commits

Reviewing files that changed from the base of the PR and between a225600 and cf4c93e.

⛔ Files ignored due to path filters (26)
  • packages/starpod/src/assets/apple.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/avatar-dark.png is excluded by !**/*.png
  • packages/starpod/src/assets/avatar-light.png is excluded by !**/*.png
  • packages/starpod/src/assets/dots-dark.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/dots-light.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/forward-icon.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/mute-icon.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/overcast.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/pocket-casts.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/rewind-icon.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/rocket-dark.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/rocket-light.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/search-icon.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/spotify.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/unmute-icon.svg is excluded by !**/*.svg
  • packages/starpod/src/assets/youtube.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/404-dark.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/404-light.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/apple.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/overcast.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/pocket-casts.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/spotify.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/ufo-dark.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/ufo-light.svg is excluded by !**/*.svg
  • packages/starpod/src/svgs/youtube.svg is excluded by !**/*.svg
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (140)
  • .github/workflows/ci.yml
  • .github/workflows/plan-release.yml
  • .github/workflows/publish.yml
  • CHANGELOG.md
  • README.md
  • RELEASE.md
  • astro.config.mjs
  • db/seed.ts
  • drizzle.config.ts
  • package.json
  • packages/starpod/.impeccable/hook.cache.json
  • packages/starpod/README.md
  • packages/starpod/cli/index.mjs
  • packages/starpod/cli/template/base/README.md
  • packages/starpod/cli/template/base/env.example
  • packages/starpod/cli/template/base/gitignore
  • packages/starpod/cli/template/base/public/.gitkeep
  • packages/starpod/cli/template/base/src/content.config.ts
  • packages/starpod/cli/template/base/src/content/transcripts/.gitkeep
  • packages/starpod/cli/template/base/src/img/people/.gitkeep
  • packages/starpod/cli/template/base/tsconfig.json
  • packages/starpod/cli/template/database/db/data/people-per-episode.ts
  • packages/starpod/cli/template/database/db/data/people.ts
  • packages/starpod/cli/template/database/db/data/sponsors-per-episode.ts
  • packages/starpod/cli/template/database/db/data/sponsors.ts
  • packages/starpod/cli/template/database/db/seed.ts
  • packages/starpod/cli/template/database/drizzle.config.ts
  • packages/starpod/package.json
  • packages/starpod/src/components/AdPackageCard.astro
  • packages/starpod/src/components/Breadcrumbs.astro
  • packages/starpod/src/components/ContactForm.tsx
  • packages/starpod/src/components/Dots.astro
  • packages/starpod/src/components/EpisodeList.astro
  • packages/starpod/src/components/FormattedDate.tsx
  • packages/starpod/src/components/FullPlayButton.tsx
  • packages/starpod/src/components/Hosts.astro
  • packages/starpod/src/components/InfoCard.astro
  • packages/starpod/src/components/LargePlatforms.astro
  • packages/starpod/src/components/NotFoundContent.astro
  • packages/starpod/src/components/Platforms.astro
  • packages/starpod/src/components/Player.tsx
  • packages/starpod/src/components/SearchButton.tsx
  • packages/starpod/src/components/SearchDialog.tsx
  • packages/starpod/src/components/ShowArtwork.astro
  • packages/starpod/src/components/episode/CreatorsAndGuests.astro
  • packages/starpod/src/components/episode/MarkdownTranscript.tsx
  • packages/starpod/src/components/episode/Sponsors.astro
  • packages/starpod/src/components/episode/Transcript.tsx
  • packages/starpod/src/components/illustrations/404Illustration.astro
  • packages/starpod/src/components/illustrations/UFOIllustration.astro
  • packages/starpod/src/components/player/ForwardButton/index.tsx
  • packages/starpod/src/components/player/ForwardButton/styles.css
  • packages/starpod/src/components/player/MuteButton/index.tsx
  • packages/starpod/src/components/player/MuteButton/styles.css
  • packages/starpod/src/components/player/PlayButton.tsx
  • packages/starpod/src/components/player/PlaybackRateButton.tsx
  • packages/starpod/src/components/player/RewindButton/index.tsx
  • packages/starpod/src/components/player/RewindButton/styles.css
  • packages/starpod/src/components/player/Slider/index.tsx
  • packages/starpod/src/components/player/Slider/styles.css
  • packages/starpod/src/components/state.ts
  • packages/starpod/src/config.ts
  • packages/starpod/src/content.ts
  • packages/starpod/src/db/index.ts
  • packages/starpod/src/db/schema.ts
  • packages/starpod/src/index.ts
  • packages/starpod/src/layouts/Layout.astro
  • packages/starpod/src/lib/api-errors.ts
  • packages/starpod/src/lib/llms.ts
  • packages/starpod/src/lib/not-found.ts
  • packages/starpod/src/lib/openapi.ts
  • packages/starpod/src/lib/optimize-episode-image.ts
  • packages/starpod/src/lib/rehype-transcript-timestamps.d.mts
  • packages/starpod/src/lib/rehype-transcript-timestamps.mjs
  • packages/starpod/src/lib/rss.ts
  • packages/starpod/src/lib/standardSite.ts
  • packages/starpod/src/lib/transcript.ts
  • packages/starpod/src/pages/.well-known/site.standard.publication.ts
  • packages/starpod/src/pages/404.astro
  • packages/starpod/src/pages/[...notFound].astro
  • packages/starpod/src/pages/[episode].astro
  • packages/starpod/src/pages/[episode].html.md.ts
  • packages/starpod/src/pages/about.astro
  • packages/starpod/src/pages/about.html.md.ts
  • packages/starpod/src/pages/api/contact.ts
  • packages/starpod/src/pages/api/episodes/[page].json.ts
  • packages/starpod/src/pages/api/episodes/search.json.ts
  • packages/starpod/src/pages/contact.astro
  • packages/starpod/src/pages/contact.html.md.ts
  • packages/starpod/src/pages/episodes-index.html.md.ts
  • packages/starpod/src/pages/for-llms.astro
  • packages/starpod/src/pages/for-llms.html.md.ts
  • packages/starpod/src/pages/index.astro
  • packages/starpod/src/pages/index.html.md.ts
  • packages/starpod/src/pages/llms.txt.ts
  • packages/starpod/src/pages/openapi.json.ts
  • packages/starpod/src/pages/robots.txt.ts
  • packages/starpod/src/styles/buttons.css
  • packages/starpod/src/styles/global.css
  • packages/starpod/src/styles/gradient-icon.css
  • packages/starpod/src/styles/search-icon.css
  • packages/starpod/src/styles/tailwind.css
  • packages/starpod/src/utils/config.ts
  • packages/starpod/src/utils/dasherize.ts
  • packages/starpod/src/utils/truncate.ts
  • packages/starpod/src/vercel-md-negotiation.d.mts
  • packages/starpod/src/vercel-md-negotiation.mjs
  • packages/starpod/src/virtual.d.ts
  • packages/starpod/tsconfig.json
  • playwright.worktree.config.ts
  • pnpm-workspace.yaml
  • scripts/publish-atproto-episodes.ts
  • src/components/player/ForwardButton/styles.css
  • src/components/player/MuteButton/styles.css
  • src/components/player/RewindButton/styles.css
  • src/content.config.ts
  • src/pages/sponsor.astro
  • src/utils/config.ts
  • starpod.config.ts
  • tests/unit/ForwardButton.test.tsx
  • tests/unit/FullPlayButton.test.tsx
  • tests/unit/MuteButton.test.tsx
  • tests/unit/PlayButton.test.tsx
  • tests/unit/PlaybackRateButton.test.tsx
  • tests/unit/Player.test.tsx
  • tests/unit/RewindButton.test.tsx
  • tests/unit/SearchButton.test.tsx
  • tests/unit/SearchDialog.test.tsx
  • tests/unit/Slider.test.tsx
  • tests/unit/config.test.ts
  • tests/unit/contact-api.test.ts
  • tests/unit/llms.test.ts
  • tests/unit/not-found.test.ts
  • tests/unit/openapi.test.ts
  • tests/unit/rehype-transcript-timestamps.test.ts
  • tests/unit/robots.test.ts
  • tests/unit/transcript.test.ts
  • tests/unit/vercel-md-negotiation.test.ts
  • tsconfig.json
  • vitest.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@RobbieTheWagner RobbieTheWagner added breaking and removed enhancement New feature or request labels Aug 29, 2026
@RobbieTheWagner
RobbieTheWagner merged commit 6ab7fb8 into main Aug 29, 2026
7 checks passed
@RobbieTheWagner
RobbieTheWagner deleted the astro-integration branch August 29, 2026 22:05
@github-actions github-actions Bot mentioned this pull request Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant