Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
5d18034
feat: centralize branding
dalelotts Dec 31, 2025
2ba67ad
revert changes to server/utils
dalelotts Dec 31, 2025
b271422
upgrade @epic-web/cachified to ^5.6.0
dalelotts Dec 31, 2025
ca0fd99
upgrade @epic-web/client-hints to ^1.3.8
dalelotts Dec 31, 2025
af3f0c4
upgrade @mjackson/headers to ^0.11.1
dalelotts Dec 31, 2025
030a079
upgrade @paralleldrive/cuid2 to ^3.0.6
dalelotts Dec 31, 2025
463d6bd
upgrade @prisma/instrumentation to ^7.2.0
dalelotts Dec 31, 2025
b7368c6
upgrade @radix-ui/react-checkbox to ^1.3.3
dalelotts Dec 31, 2025
cde5668
upgrade @radix-ui/react-dropdown-menu to ^2.1.16
dalelotts Dec 31, 2025
ee83a1d
upgrade @radix-ui/react-label to ^2.1.8
dalelotts Dec 31, 2025
f820b7f
upgrade @radix-ui/react-slot to ^1.2.4
dalelotts Dec 31, 2025
91234ea
upgrade @radix-ui/react-toast to ^1.2.15
dalelotts Dec 31, 2025
dd90f9f
upgrade @radix-ui/react-tooltip to ^1.2.8
dalelotts Dec 31, 2025
250e8ea
upgrade @react-email/components to 1.0.3
dalelotts Dec 31, 2025
9e15a1d
upgrade @remix-run/server-runtime to ^2.17.2
dalelotts Dec 31, 2025
bb7628c
upgrade @simplewebauthn/browser to ^13.2.2
dalelotts Dec 31, 2025
9fb61a8
upgrade @simplewebauthn/server to ^13.2.2
dalelotts Dec 31, 2025
a0b36d6
upgrade @tailwindcss/vite to ^4.1.18
dalelotts Dec 31, 2025
1561bb3
upgrade bcryptjs to ^3.0.3
dalelotts Dec 31, 2025
55210e2
upgrade close-with-grace to ^2.4.0
dalelotts Dec 31, 2025
cdb99f5
upgrade compression to ^1.8.1
dalelotts Dec 31, 2025
9a6c697
upgrade cookie to ^1.1.1
dalelotts Dec 31, 2025
ba50de7
upgrade cross-env to ^10.1.0
dalelotts Dec 31, 2025
c3232ae
upgrade dotenv to ^17.2.3
dalelotts Dec 31, 2025
fccb388
upgrade execa to ^9.6.1
dalelotts Dec 31, 2025
1986270
upgrade express-rate-limit to ^8.2.1
dalelotts Dec 31, 2025
c66ccbf
upgrade glob to ^13.0.0
dalelotts Dec 31, 2025
79f6078
upgrade isbot to ^5.1.32
dalelotts Dec 31, 2025
91b4830
upgrade lru-cache to ^11.2.4
dalelotts Dec 31, 2025
183cc34
upgrade mime-types to ^3.0.2
dalelotts Dec 31, 2025
ee86f6b
upgrade morgan to ^1.10.1
dalelotts Dec 31, 2025
7e6baa6
upgrade prisma to ^7.2.0
dalelotts Dec 31, 2025
169b991
upgrade remix-utils to ^9.0.0
dalelotts Dec 31, 2025
a057f2b
upgrade set-cookie-parser to ^2.7.2
dalelotts Dec 31, 2025
61084a3
upgrade sharp to ^0.34.5
dalelotts Dec 31, 2025
c84ab0f
upgrade sonner to ^2.0.7
dalelotts Dec 31, 2025
4b064a7
upgrade tailwind-merge to ^3.4.0
dalelotts Dec 31, 2025
14202c7
upgrade tailwindcss to ^4.1.18
dalelotts Dec 31, 2025
0560baf
upgrade @epic-web/config to ^1.21.3
dalelotts Dec 31, 2025
132bf9f
upgrade @faker-js/faker to ^10.1.0
dalelotts Dec 31, 2025
73a1334
upgrade @playwright/test to ^1.57.0
dalelotts Dec 31, 2025
f2ba4fe
upgrade @testing-library/dom to ^10.4.1
dalelotts Dec 31, 2025
9728dcd
upgrade @testing-library/jest-dom to ^6.9.1
dalelotts Dec 31, 2025
8011221
upgrade @testing-library/react to ^16.3.1
dalelotts Dec 31, 2025
5fefc6d
upgrade @types/compression to ^1.8.1
dalelotts Dec 31, 2025
0f50e83
upgrade @types/express to ^5.0.6
dalelotts Dec 31, 2025
e9d5746
upgrade @types/glob to ^9.0.0
dalelotts Dec 31, 2025
1893795
upgrade @types/mime-types to ^3.0.1
dalelotts Dec 31, 2025
c7904ef
upgrade @types/morgan to ^1.9.10
dalelotts Dec 31, 2025
1f217e5
upgrade @types/node to ^25.0.3
dalelotts Dec 31, 2025
40af597
upgrade @types/qrcode to ^1.5.6
dalelotts Dec 31, 2025
570f652
upgrade @types/react to ^19.2.7
dalelotts Dec 31, 2025
9be3d0f
upgrade @types/react-dom to ^19.2.3
dalelotts Dec 31, 2025
eee5736
upgrade @vitejs/plugin-react to ^5.1.2
dalelotts Dec 31, 2025
61b1af0
upgrade @vitest/coverage-v8 to ^4.0.16
dalelotts Dec 31, 2025
25085a1
upgrade esbuild to ^0.27.2
dalelotts Dec 31, 2025
d762078
upgrade eslint to ^9.39.2
dalelotts Dec 31, 2025
ede935d
upgrade fs-extra to ^11.3.3
dalelotts Dec 31, 2025
a22ca04
upgrade jsdom to ^27.4.0
dalelotts Dec 31, 2025
d107fed
upgrade msw to ^2.12.7
dalelotts Dec 31, 2025
b9f83bc
upgrade prettier to ^3.7.4
dalelotts Dec 31, 2025
e63c174
upgrade prettier-plugin-sql to ^0.19.2
dalelotts Dec 31, 2025
d0c50de
upgrade prettier-plugin-tailwindcss to ^0.7.2
dalelotts Dec 31, 2025
57f46df
upgrade react-router-auto-routes to ^0.7.1
dalelotts Dec 31, 2025
afc6d24
upgrade react-router-devtools to ^6.0.1
dalelotts Dec 31, 2025
fa73a69
upgrade tsx to ^4.21.0
dalelotts Dec 31, 2025
8db334a
upgrade tw-animate-css to ^1.4.0
dalelotts Dec 31, 2025
c655c66
upgrade vite to ^7.3.0
dalelotts Dec 31, 2025
db42a5c
feat: add coverage to e2e test
dalelotts Jan 2, 2026
c406ac7
WIP
dalelotts Jan 2, 2026
49030df
WIP
dalelotts Jan 2, 2026
c22454a
WIP
dalelotts Jan 2, 2026
a332670
WIP
dalelotts Jan 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="super-duper-s3cret"
HONEYPOT_SECRET="super-duper-s3cret"
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
SENTRY_DSN="your-dsn"
#SENTRY_DSN="your-dsn"

# this is set to a random value in the Dockerfile
INTERNAL_COMMAND_TOKEN="some-made-up-token"
Expand Down
193 changes: 120 additions & 73 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name: 🚀 Deploy

on:
push:
branches:
- main
- dev
branches: [main, dev]
pull_request: {}

concurrency:
Expand All @@ -30,12 +29,6 @@ jobs:
- name: 📥 Download deps
uses: bahmutov/npm-install@v1

- name: 🏄 Copy test env vars
run: cp .env.example .env

- name: 🛠 Setup Database
run: npx prisma migrate deploy && npx prisma generate --sql

- name: 🔬 Lint
run: npm run lint

Expand All @@ -54,15 +47,6 @@ jobs:
- name: 📥 Download deps
uses: bahmutov/npm-install@v1

- name: 🏗 Build
run: npm run build

- name: 🏄 Copy test env vars
run: cp .env.example .env

- name: 🛠 Setup Database
run: npx prisma migrate deploy && npx prisma generate --sql

- name: 🔎 Type check
run: npm run typecheck --if-present

Expand All @@ -84,9 +68,6 @@ jobs:
- name: 🏄 Copy test env vars
run: cp .env.example .env

- name: 🛠 Setup Database
run: npx prisma migrate deploy && npx prisma generate --sql

- name: ⚡ Run vitest
run: npm run test -- --coverage

Expand All @@ -113,7 +94,7 @@ jobs:
run: npm run test:e2e:install

- name: 🛠 Setup Database
run: npx prisma migrate deploy && npx prisma generate --sql
run: npx prisma migrate deploy

- name: 🏦 Cache Database
id: db-cache
Expand Down Expand Up @@ -143,11 +124,26 @@ jobs:
path: playwright-report/
retention-days: 30

container:
name: 📦 Prepare Container
runs-on: ubuntu-24.04
# only prepare container on pushes
if: ${{ github.event_name == 'push' }}
prepare_deploy:
name: 🧰 Prepare deploy metadata
runs-on: ubuntu-22.04
needs: [lint, typecheck, vitest, playwright]
if: >-
(
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
) || (
github.event_name == 'pull_request' &&
(
github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize' ||
github.event.action == 'ready_for_review'
) &&
github.event.pull_request.head.repo.fork == false
)
outputs:
base_app_name: ${{ steps.app_name.outputs.value }}
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
Expand All @@ -158,72 +154,123 @@ jobs:
uses: SebRollen/[email protected]
id: app_name
with:
file: 'fly.toml'
field: 'app'
file: fly.toml
field: app

deploy_preview:
name: 👀 Deploy Preview
runs-on: ubuntu-22.04
needs: [prepare_deploy]
if: >-
github.event_name == 'pull_request' && (
github.event.action == 'opened' ||
github.event.action == 'reopened' ||
github.event.action == 'synchronize' ||
github.event.action == 'ready_for_review'
) && github.event.pull_request.head.repo.fork == false
environment:
name: preview
url:
https://${{ needs.prepare_deploy.outputs.base_app_name }}-pr-${{
github.event.pull_request.number }}.fly.dev
permissions:
contents: read
pull-requests: write
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 50

- name: 🎈 Setup Fly
uses: superfly/flyctl-actions/[email protected]

- name: 📦 Build Staging Container
if: ${{ github.ref == 'refs/heads/dev' }}
run: |
flyctl deploy \
--build-only \
--push \
--image-label ${{ github.sha }} \
--build-arg COMMIT_SHA=${{ github.sha }} \
--app ${{ steps.app_name.outputs.value }}-staging
- name: 🚀 Deploy Preview (remote build)
shell: bash
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

- name: 📦 Build Production Container
if: ${{ github.ref == 'refs/heads/main' }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail

flyctl deploy \
--build-only \
--push \
--image-label ${{ github.sha }} \
--build-arg COMMIT_SHA=${{ github.sha }} \
--build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \
--app ${{ steps.app_name.outputs.value }}
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
--remote-only \
--build-arg "COMMIT_SHA=${{ github.sha }}" \
--build-secret "SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}" \
--app "${{ needs.prepare_deploy.outputs.base_app_name }}-pr-${{ github.event.pull_request.number }}"

- name: 💬 Comment preview URL
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
✅ Preview deployed
🔗 https://${{ needs.prepare_deploy.outputs.base_app_name }}-pr-${{ github.event.pull_request.number }}.fly.dev

deploy:
name: 🚀 Deploy
runs-on: ubuntu-24.04
needs: [lint, typecheck, vitest, playwright, container]
# only deploy on pushes
if: ${{ github.event_name == 'push' }}
deploy_staging:
name: 🚀 Deploy Staging
runs-on: ubuntu-22.04
needs: [prepare_deploy]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
environment:
name: staging
url:
https://${{ needs.prepare_deploy.outputs.base_app_name
}}-staging.fly.dev
permissions:
contents: read
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: '50'

- name: 👀 Read app name
uses: SebRollen/[email protected]
id: app_name
with:
file: 'fly.toml'
field: 'app'
fetch-depth: 50

- name: 🎈 Setup Fly
uses: superfly/flyctl-actions/[email protected]

- name: 🚀 Deploy Staging
if: ${{ github.ref == 'refs/heads/dev' }}
run: |
flyctl deploy \
--image "registry.fly.io/${{ steps.app_name.outputs.value }}-staging:${{ github.sha }}" \
--app ${{ steps.app_name.outputs.value }}-staging
- name: 🚀 Deploy Staging (remote build)
shell: bash
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

- name: 🚀 Deploy Production
if: ${{ github.ref == 'refs/heads/main' }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail

flyctl deploy \
--image "registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.sha }}"
--remote-only \
--build-arg "COMMIT_SHA=${{ github.sha }}" \
--build-secret "SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}" \
--app "${{ needs.prepare_deploy.outputs.base_app_name }}-staging"

deploy_production:
name: 🚀 Deploy Production
runs-on: ubuntu-22.04
needs: [prepare_deploy]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
environment:
name: production
url: https://${{ needs.prepare_deploy.outputs.base_app_name }}.fly.dev
permissions:
contents: read
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 50

- name: 🎈 Setup Fly
uses: superfly/flyctl-actions/[email protected]

- name: 🚀 Deploy Production (remote build)
shell: bash
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail

flyctl deploy \
--remote-only \
--build-arg "COMMIT_SHA=${{ github.sha }}" \
--build-secret "SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}" \
--app "${{ needs.prepare_deploy.outputs.base_app_name }}"
46 changes: 46 additions & 0 deletions .github/workflows/preview-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: 🧹 Preview Cleanup

on:
pull_request:
types: [closed]

jobs:
destroy:
name: 🗑 Remove Preview App
runs-on: ubuntu-24.04
if: ${{ github.event.pull_request.head.repo.fork == false }}

permissions:
pull-requests: write
contents: read

steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4

- name: 🔤 Read Fly app name
id: base_app
uses: SebRollen/[email protected]
with:
file: fly.toml
field: app

- name: 🎈 Setup Fly
uses: superfly/flyctl-actions/[email protected]

- name: 🗑 Destroy preview app
shell: bash
run: |
set -euo pipefail
preview_app_name="${{ steps.base_app.outputs.value }}-pr-${{ github.event.pull_request.number }}"

flyctl apps destroy "${preview_app_name}" --yes || echo "App already removed"
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

- name: 💬 Notify pull request
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
♻️ Preview environment `${{ steps.base_app.outputs.value }}-pr-${{ github.event.pull_request.number }}` has been destroyed.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.DS_store
.idea

/build
.env
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ Want to talk about the Epic Stack in a blog post or talk? Great! Here are some
assets you can use in your material:
[EpicWeb.dev/brand](https://epicweb.dev/brand)

## App Branding Configuration

- Update `app/utils/branding.ts` to change the app title, base URL, or email
sender (swap the defaults for `import.meta.env.*` / `process.env.*` when
you're ready to drive them from environment variables).
- Set `PREVIEW_BADGE_TEXT` (or `FLY_PREVIEW_PREVIEW_BADGE_TEXT` in the preview
workflow) to show a thin banner across the top of the app that reminds people
they're viewing a preview deploy.

## E2E Coverage

- Run `npm run test:e2e:run` (or `npm run test:e2e:dev`) and open
`coverage/e2e/index.html` to explore the Monocart report and the captured
Chromium coverage.

## Thanks

You rock 🪨
37 changes: 37 additions & 0 deletions app/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'

import { cn } from '#app/utils/misc.tsx'

const badgeVariants = cva(
'focus:ring-ring inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)

export interface BadgeProps
extends
React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}

export { Badge, badgeVariants }
3 changes: 2 additions & 1 deletion app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { startTransition } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { HydratedRouter } from 'react-router/dom'
import { init } from './utils/monitoring.client.tsx'

if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
init()
}

startTransition(() => {
Expand Down
Loading