From e95762d9d8a7bdd506753601dd285d60e7e77594 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 28 Oct 2025 10:50:25 -0400 Subject: [PATCH 1/2] Make db optional --- astro.config.no-db.mjs | 86 +++++++++++++++++++++++++++ package.json | 2 + playwright.config.ts | 41 +++++++++---- src/pages/[episode].astro | 78 +++++++++++++----------- tests/e2e-no-db/episode-no-db.spec.ts | 63 ++++++++++++++++++++ tests/e2e/episode.spec.ts | 38 ++++++++++++ 6 files changed, 262 insertions(+), 46 deletions(-) create mode 100644 astro.config.no-db.mjs create mode 100644 tests/e2e-no-db/episode-no-db.spec.ts diff --git a/astro.config.no-db.mjs b/astro.config.no-db.mjs new file mode 100644 index 0000000..be17c62 --- /dev/null +++ b/astro.config.no-db.mjs @@ -0,0 +1,86 @@ +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'; + +// https://astro.build/config +export default defineConfig({ + output: 'static', + adapter: vercel({ + imageService: true, + imagesConfig: { + formats: ['image/avif'], + minimumCacheTTL: 3600, + remotePatterns: [ + { + protocol: 'https' + }, + { + protocol: 'http' + } + ], + sizes: [160, 320, 640, 1280] + }, + webAnalytics: { + enabled: true + } + }), + build: { + inlineStylesheets: 'always' + }, + experimental: { + clientPrerender: true, + fonts: [ + { + provider: fontProviders.google({ + experimental: { variableAxis: { Inter: { opsz: ['14..32'] } } } + }), + name: 'Inter', + cssVariable: '--astro-font-inter', + weights: ['300 900'], + styles: ['normal'], + subsets: ['latin'] + } + ] + }, + image: { + remotePatterns: [ + { + protocol: 'https' + }, + { + protocol: 'http' + } + ] + }, + prefetch: { + prefetchAll: true, + defaultStrategy: 'viewport' + }, + site: 'https://whiskey.fm', + trailingSlash: 'never', + integrations: [ + // Note: db() integration is intentionally removed for testing without astro:db + preact(), + sitemap({ + filter: (page) => { + const pathname = new URL(page).pathname; + // Exclude episode number pages and only include slug pages. + return !/^\/\d+\/?$/.test(pathname); + } + }) + ], + // 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': + '/hot-takes-tanstack-and-open-source-with-tanner-linsley', + '/creating-code-pen-tackling-tailwind-and-keeping-it-simple-with-chris-coyier': + '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/package.json b/package.json index dea2ecb..71c9adc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "start": "astro dev", "test": "concurrently \"pnpm:test:*(!fix)\" --names \"test:\"", "test:e2e": "pnpm exec playwright test", + "test:e2e:with-db": "pnpm exec playwright test --project=chromium-with-db", + "test:e2e:no-db": "pnpm exec playwright test --project=chromium-no-db", "test:unit": "vitest" }, "dependencies": { diff --git a/playwright.config.ts b/playwright.config.ts index 30ac416..ab1ca55 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -33,18 +33,30 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] } + name: 'chromium-with-db', + use: { ...devices['Desktop Chrome'] }, + testDir: './tests/e2e' }, { - name: 'firefox', - use: { ...devices['Desktop Firefox'] } + name: 'firefox-with-db', + use: { ...devices['Desktop Firefox'] }, + testDir: './tests/e2e' }, { - name: 'webkit', - use: { ...devices['Desktop Safari'] } + name: 'webkit-with-db', + use: { ...devices['Desktop Safari'] }, + testDir: './tests/e2e' + }, + + { + name: 'chromium-no-db', + use: { + ...devices['Desktop Chrome'], + baseURL: 'http://localhost:4322' + }, + testDir: './tests/e2e-no-db' } /* Test against mobile viewports. */ @@ -69,9 +81,16 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm dev', - url: 'http://localhost:4321', - reuseExistingServer: !process.env.CI - } + webServer: [ + { + command: 'pnpm dev', + url: 'http://localhost:4321', + reuseExistingServer: !process.env.CI + }, + { + command: 'pnpm astro dev --config astro.config.no-db.mjs --port 4322', + url: 'http://localhost:4322', + reuseExistingServer: !process.env.CI + } + ] }); diff --git a/src/pages/[episode].astro b/src/pages/[episode].astro index a4fa627..a2193ab 100644 --- a/src/pages/[episode].astro +++ b/src/pages/[episode].astro @@ -1,14 +1,14 @@ --- import { getEntry } from 'astro:content'; -import { - db, - eq, - Episode as DbEpisode, - HostOrGuest, - Person, - Sponsor, - SponsorForEpisode -} from 'astro:db'; + +// Check if astro:db integration is available by looking for the virtual module +let astroDB; + +try { + astroDB = await import('astro:db'); +} catch { + // astro:db is not available +} import { Schema } from 'astro-seo-schema'; @@ -55,32 +55,40 @@ if (episode.episodeNumber && episode.episodeNumber !== 'Bonus') { const canonicalURL = new URL(`/${episode.episodeSlug}`, Astro.url); -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)); +let hostsAndGuests: any[] = []; +let sponsors: any[] = []; + +if (astroDB) { + // @ts-ignore - These table exports only exist when astro:db integration is enabled + const { db, eq, Episode: DbEpisode, HostOrGuest, Person, Sponsor, SponsorForEpisode } = astroDB; + + 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)); + + 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)); +} const title = `${episode.title} - ${show.title} - Episode ${episode.episodeNumber}`; --- diff --git a/tests/e2e-no-db/episode-no-db.spec.ts b/tests/e2e-no-db/episode-no-db.spec.ts new file mode 100644 index 0000000..d6e7f0b --- /dev/null +++ b/tests/e2e-no-db/episode-no-db.spec.ts @@ -0,0 +1,63 @@ +import { expect, test } from '@playwright/test'; + +const episode1 = { + title: + 'Throwback Frameworks, Tailwind Fandom, and CSS with Jhey Tompkins - Whiskey Web and Whatnot: Web Development, Neat - Episode 120', + description: + /^Have you ever reflected on the tools that shaped your journey as a developer?/, + image: + 'https://play.cdnstream1.com/zjb/image/download/d6/c4/9e/d6c49e16-ed89-45f9-b5d4-60dac61e44b0_1400.jpg' +}; + +test('episode page works without astro:db sections', async ({ page }) => { + await page.goto('/120'); + + // Core episode content should always be present + await expect(page.locator('h1')).toContainText('120:'); + await expect(page.locator('h3:has-text("Show Notes")')).toBeVisible(); + await expect(page.locator('h3:has-text("Episode Transcript")')).toBeVisible(); + + // The page should load successfully + await expect(page).toHaveTitle(episode1.title); +}); + +test('hosts and guests section should not appear without astro:db', async ({ + page +}) => { + await page.goto('/120'); + + // Check that the "Creators and Guests" section does NOT exist + const creatorsSection = page.locator('h3:has-text("Creators and Guests")'); + await expect(creatorsSection).not.toBeVisible(); +}); + +test('sponsors section should not appear without astro:db', async ({ + page +}) => { + await page.goto('/120'); + + // Check that the "Sponsors" section does NOT exist + const sponsorsSection = page.locator('h3:has-text("Sponsors")'); + await expect(sponsorsSection).not.toBeVisible(); +}); + +test('episode page meta data works without astro:db', async ({ page }) => { + await page.goto('/120'); + + await expect(page).toHaveTitle(episode1.title); + + const ogTitle = page.locator('meta[property="og:title"]'); + await expect(ogTitle).toHaveAttribute('content', episode1.title); + + const twitterTitle = page.locator('meta[name="twitter:title"]'); + await expect(twitterTitle).toHaveAttribute('content', episode1.title); + + const description = page.locator('meta[name="description"]'); + await expect(description).toHaveAttribute('content', episode1.description); + + const ogImage = page.locator('meta[property="og:image"]'); + await expect(ogImage).toHaveAttribute('content', episode1.image); + + const twitterImage = page.locator('meta[name="twitter:image:src"]'); + await expect(twitterImage).toHaveAttribute('content', episode1.image); +}); diff --git a/tests/e2e/episode.spec.ts b/tests/e2e/episode.spec.ts index acefe3a..9e235d5 100644 --- a/tests/e2e/episode.spec.ts +++ b/tests/e2e/episode.spec.ts @@ -82,3 +82,41 @@ test('works for bonus episodes with no episode number', async ({ page }) => { const twitterImage = page.locator('meta[name="twitter:image:src"]'); await expect(twitterImage).toHaveAttribute('content', episode2.image); }); + +test('displays hosts and guests when astro:db is available', async ({ + page +}) => { + await page.goto('/120'); + + // Check if the "Creators and Guests" section exists + const creatorsSection = page.locator('h3:has-text("Creators and Guests")'); + await expect(creatorsSection).toBeVisible(); + + // Check if at least one host/guest is displayed in the grid + const hostGuestGrid = page.locator( + 'h3:has-text("Creators and Guests") + div' + ); + await expect(hostGuestGrid).toBeVisible(); + + // Check that there are actual host/guest items + const hostGuestItems = page.locator( + 'h3:has-text("Creators and Guests") + div > div' + ); + await expect(hostGuestItems.first()).toBeVisible(); +}); + +test('displays sponsors when astro:db is available', async ({ page }) => { + await page.goto('/120'); + + // Check if the "Sponsors" section exists + const sponsorsSection = page.locator('h3:has-text("Sponsors")'); + await expect(sponsorsSection).toBeVisible(); + + // Check if at least one sponsor is displayed in the grid + const sponsorGrid = page.locator('h3:has-text("Sponsors") + div'); + await expect(sponsorGrid).toBeVisible(); + + // Check that there are actual sponsor items + const sponsorItems = page.locator('h3:has-text("Sponsors") + div > a'); + await expect(sponsorItems.first()).toBeVisible(); +}); From aeaedc36235f898a4145a51a68300b6968760021 Mon Sep 17 00:00:00 2001 From: Robert Wagner Date: Tue, 28 Oct 2025 11:02:02 -0400 Subject: [PATCH 2/2] Extend config --- astro.config.mjs | 11 +++-- astro.config.no-db.mjs | 92 ++++-------------------------------------- package.json | 1 + tsconfig.no-db.json | 14 +++++++ 4 files changed, 30 insertions(+), 88 deletions(-) create mode 100644 tsconfig.no-db.json diff --git a/astro.config.mjs b/astro.config.mjs index 66f2ecd..4090ec7 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -5,8 +5,8 @@ import sitemap from '@astrojs/sitemap'; import tailwindcss from '@tailwindcss/vite'; import vercel from '@astrojs/vercel'; -// https://astro.build/config -export default defineConfig({ +// Shared base configuration (without db) +export const baseConfig = { output: 'static', adapter: vercel({ imageService: true, @@ -62,7 +62,6 @@ export default defineConfig({ site: 'https://whiskey.fm', trailingSlash: 'never', integrations: [ - db(), preact(), sitemap({ filter: (page) => { @@ -84,4 +83,10 @@ export default defineConfig({ vite: { plugins: [tailwindcss()] } +}; + +// https://astro.build/config - Default configuration with astro:db +export default defineConfig({ + ...baseConfig, + integrations: [db(), ...baseConfig.integrations] }); diff --git a/astro.config.no-db.mjs b/astro.config.no-db.mjs index be17c62..a23724c 100644 --- a/astro.config.no-db.mjs +++ b/astro.config.no-db.mjs @@ -1,86 +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'; -// https://astro.build/config -export default defineConfig({ - output: 'static', - adapter: vercel({ - imageService: true, - imagesConfig: { - formats: ['image/avif'], - minimumCacheTTL: 3600, - remotePatterns: [ - { - protocol: 'https' - }, - { - protocol: 'http' - } - ], - sizes: [160, 320, 640, 1280] - }, - webAnalytics: { - enabled: true - } - }), - build: { - inlineStylesheets: 'always' - }, - experimental: { - clientPrerender: true, - fonts: [ - { - provider: fontProviders.google({ - experimental: { variableAxis: { Inter: { opsz: ['14..32'] } } } - }), - name: 'Inter', - cssVariable: '--astro-font-inter', - weights: ['300 900'], - styles: ['normal'], - subsets: ['latin'] - } - ] - }, - image: { - remotePatterns: [ - { - protocol: 'https' - }, - { - protocol: 'http' - } - ] - }, - prefetch: { - prefetchAll: true, - defaultStrategy: 'viewport' - }, - site: 'https://whiskey.fm', - trailingSlash: 'never', - integrations: [ - // Note: db() integration is intentionally removed for testing without astro:db - preact(), - sitemap({ - filter: (page) => { - const pathname = new URL(page).pathname; - // Exclude episode number pages and only include slug pages. - return !/^\/\d+\/?$/.test(pathname); - } - }) - ], - // 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': - '/hot-takes-tanstack-and-open-source-with-tanner-linsley', - '/creating-code-pen-tackling-tailwind-and-keeping-it-simple-with-chris-coyier': - '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()] - } -}); +// Import the shared base configuration (which doesn't include db) +import { baseConfig } from './astro.config.mjs'; + +// https://astro.build/config - Configuration without astro:db for testing +// Note: Use with --tsconfig tsconfig.no-db.json to exclude db files from type checking +export default defineConfig(baseConfig); diff --git a/package.json b/package.json index 71c9adc..7e7b231 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "scripts": { "astro": "astro", "build": "astro check && astro build --remote", + "check:no-db": "astro check --config astro.config.no-db.mjs --tsconfig tsconfig.no-db.json", "db:seed": "pnpm astro db execute db/seed.ts --remote", "dev": "astro dev", "lint": "eslint . --cache", diff --git a/tsconfig.no-db.json b/tsconfig.no-db.json new file mode 100644 index 0000000..79df8f9 --- /dev/null +++ b/tsconfig.no-db.json @@ -0,0 +1,14 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "esModuleInterop": true, + "strictNullChecks": true, + "verbatimModuleSyntax": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" + }, + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist", "db"] +}