Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
bcd4100
chore(deps): update @conform-to/react
cursoragent Jan 29, 2026
69fca5f
chore(deps): update @conform-to/zod
cursoragent Jan 29, 2026
e29b744
chore(deps): update @epic-web/cachified
cursoragent Jan 29, 2026
b4fb401
chore(deps): update @epic-web/client-hints
cursoragent Jan 29, 2026
375d53d
chore(deps): update @mjackson/form-data-parser
cursoragent Jan 29, 2026
d41d459
chore(deps): update @mjackson/headers
cursoragent Jan 29, 2026
1983a82
chore(deps): update @paralleldrive/cuid2
cursoragent Jan 29, 2026
e32070b
chore(deps): update @prisma/client
cursoragent Jan 29, 2026
63c3f0d
chore(deps): update @prisma/instrumentation
cursoragent Jan 29, 2026
fc5b515
chore(deps): update @radix-ui/react-checkbox
cursoragent Jan 29, 2026
468e07c
chore(deps): update @radix-ui/react-dropdown-menu
cursoragent Jan 29, 2026
00d44ef
chore(deps): update @radix-ui/react-label
cursoragent Jan 29, 2026
a305ca8
chore(deps): update @radix-ui/react-slot
cursoragent Jan 29, 2026
7e5f7a7
chore(deps): update @radix-ui/react-toast
cursoragent Jan 29, 2026
7c0495c
chore(deps): update @radix-ui/react-tooltip
cursoragent Jan 29, 2026
85619b1
chore(deps): update @react-email/components
cursoragent Jan 29, 2026
2b93fb6
chore(deps): update @react-router/express
cursoragent Jan 29, 2026
7f5889a
chore(deps): update @react-router/node
cursoragent Jan 29, 2026
5a505f9
chore(deps): update @remix-run/server-runtime
cursoragent Jan 29, 2026
9c15945
chore(deps): update @sentry/profiling-node
cursoragent Jan 29, 2026
9ead7c8
chore(deps): update @sentry/react-router
cursoragent Jan 29, 2026
afc205f
chore(deps): update @simplewebauthn/browser
cursoragent Jan 29, 2026
448aef5
chore(deps): update @simplewebauthn/server
cursoragent Jan 29, 2026
b0fe231
chore(deps): update @tailwindcss/vite
cursoragent Jan 29, 2026
7e5f47e
chore(deps): update @tusbar/cache-control
cursoragent Jan 29, 2026
b7bfa4d
chore(deps): update bcryptjs
cursoragent Jan 29, 2026
e8271a3
chore(deps): update close-with-grace
cursoragent Jan 29, 2026
a7dc481
chore(deps): update compression
cursoragent Jan 29, 2026
0c938cc
chore(deps): update cookie
cursoragent Jan 29, 2026
b99410e
chore(deps): update cross-env
cursoragent Jan 29, 2026
f06342a
chore(deps): update dotenv
cursoragent Jan 29, 2026
70bb402
chore(deps): update execa
cursoragent Jan 29, 2026
3692710
chore(deps): update express
cursoragent Jan 29, 2026
c7805a2
chore(deps): update express-rate-limit
cursoragent Jan 29, 2026
12d03bf
chore(deps): update glob
cursoragent Jan 29, 2026
6d4edcc
chore(deps): update isbot
cursoragent Jan 29, 2026
51f9f19
chore(deps): update lru-cache
cursoragent Jan 29, 2026
725eca0
chore(deps): update morgan
cursoragent Jan 29, 2026
f97ed15
chore(deps): update prisma
cursoragent Jan 29, 2026
6692a0d
chore(deps): update react
cursoragent Jan 29, 2026
f3300f5
chore(deps): update react-dom
cursoragent Jan 29, 2026
1811a02
chore(deps): update react-router
cursoragent Jan 29, 2026
4b7dd66
chore(deps): update remix-utils
cursoragent Jan 29, 2026
19045c5
chore(deps): update set-cookie-parser
cursoragent Jan 29, 2026
e650260
chore(deps): update sharp
cursoragent Jan 29, 2026
9b848ac
chore(deps): update sonner
cursoragent Jan 29, 2026
4e663af
chore(deps): update tailwind-merge
cursoragent Jan 29, 2026
5a6c71c
chore(deps): update zod
cursoragent Jan 29, 2026
96e8a9b
chore(dev-deps): update @epic-web/config
cursoragent Jan 29, 2026
5605d72
chore(dev-deps): update @faker-js/faker
cursoragent Jan 29, 2026
816e493
chore(dev-deps): update @playwright/test
cursoragent Jan 29, 2026
245f169
chore(dev-deps): update @react-router/dev
cursoragent Jan 29, 2026
e31237a
chore(dev-deps): update @testing-library/dom
cursoragent Jan 29, 2026
1fc02e7
chore(dev-deps): update @testing-library/jest-dom
cursoragent Jan 29, 2026
516cc6f
chore(dev-deps): update @testing-library/react
cursoragent Jan 29, 2026
d9dbb4b
chore(dev-deps): update @types/express
cursoragent Jan 29, 2026
b54d800
chore(dev-deps): update @types/mime-types
cursoragent Jan 29, 2026
22f0b53
chore(dev-deps): update @types/node
cursoragent Jan 29, 2026
b7e5402
chore(dev-deps): update @types/qrcode
cursoragent Jan 29, 2026
bcc85e2
chore(dev-deps): update @types/react
cursoragent Jan 29, 2026
9243265
chore(dev-deps): update @types/react-dom
cursoragent Jan 29, 2026
71752d1
chore(dev-deps): update @vitejs/plugin-react
cursoragent Jan 29, 2026
fdc4215
chore(dev-deps): update @vitest/coverage-v8
cursoragent Jan 29, 2026
8e64482
chore(dev-deps): update esbuild
cursoragent Jan 29, 2026
d231c72
chore(dev-deps): update eslint
cursoragent Jan 29, 2026
947941d
chore(dev-deps): update fs-extra
cursoragent Jan 29, 2026
490bf6e
chore(dev-deps): update jsdom
cursoragent Jan 29, 2026
b1495c9
chore(dev-deps): update msw
cursoragent Jan 29, 2026
5ad8595
chore(dev-deps): update prettier
cursoragent Jan 29, 2026
3d4f43a
chore(dev-deps): update prettier-plugin-sql
cursoragent Jan 29, 2026
da21e72
chore(dev-deps): update prettier-plugin-tailwindcss
cursoragent Jan 29, 2026
9486a38
chore(dev-deps): update react-router-auto-routes
cursoragent Jan 29, 2026
2ae8ed4
chore(dev-deps): update react-router-devtools
cursoragent Jan 29, 2026
cf09f69
chore(dev-deps): update tsx
cursoragent Jan 29, 2026
a9f3876
chore(dev-deps): update tw-animate-css
cursoragent Jan 29, 2026
f3a676b
chore(dev-deps): update typescript
cursoragent Jan 29, 2026
c420fb0
chore(dev-deps): update vite
cursoragent Jan 29, 2026
b77aed8
chore(dev-deps): update vitest
cursoragent Jan 29, 2026
7be36b1
chore(deps): revert zod to v3
cursoragent Jan 29, 2026
bfb1539
chore(test): extend playwright webServer timeout
cursoragent Jan 29, 2026
70038fd
fix(server): update wildcard route for express 5
cursoragent Jan 29, 2026
31c8576
fix(server): update static wildcard routes
cursoragent Jan 29, 2026
546bf91
fix(server): update rate-limit key generator
cursoragent Jan 29, 2026
23e99db
chore(test): add env defaults for e2e
cursoragent Jan 29, 2026
978e5ec
chore(deps): revert @prisma/client to v6
cursoragent Jan 29, 2026
9bd8b14
chore(deps): revert @prisma/instrumentation to v6
cursoragent Jan 29, 2026
3871695
chore(deps): revert prisma to v6
cursoragent Jan 29, 2026
0cb23e0
chore(test): reuse env defaults for playwright
cursoragent Jan 29, 2026
a67ffe7
chore(test): add litefs env default
cursoragent Jan 29, 2026
ae44138
fix(server): use express-rate-limit ipKeyGenerator
cursoragent Jan 29, 2026
1de8367
fix(ci): address typecheck and vitest errors
cursoragent Jan 29, 2026
c3351b0
fix(test): use in-memory cache db in tests
cursoragent Jan 29, 2026
461793d
fix(ci): drop in-memory cache and downgrade vitest
cursoragent Jan 29, 2026
563442f
fix(test): isolate cache db per vitest worker
cursoragent Jan 29, 2026
8d354f2
fix(test): set cache path in test setup
cursoragent Jan 29, 2026
37ff1e9
fix(test): provide defaults for env vars
cursoragent Jan 29, 2026
a14a5fa
fix(test): set github env defaults
cursoragent Jan 29, 2026
7e62030
chore(test): rely on .env for config
cursoragent Jan 30, 2026
54e0fd0
fix(test): keep playwright webServer env override
cursoragent Jan 30, 2026
ffa164e
Fix fetch body handling for FileUpload objects in uploadToStorage
cursoragent Jan 30, 2026
fb6b060
Changes from background agent bc-82152cf3-d1b0-43fd-9143-0f9fe221fc9b
cursoragent Jan 30, 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
29 changes: 13 additions & 16 deletions app/routes/_auth/auth.$provider/callback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { loader } from './callback.ts'

const ROUTE_PATH = '/auth/github/callback'
const PARAMS = { provider: 'github' }
const LOADER_ARGS_BASE = {
params: PARAMS,
context: {} as AppLoadContext,
unstable_pattern: ROUTE_PATH,
}

afterEach(async () => {
await deleteGitHubUsers()
Expand All @@ -28,8 +33,7 @@ test('a new user goes to onboarding', async () => {
const request = await setupRequest()
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
}).catch((e) => e)
expect(response).toHaveRedirect('/onboarding/github')
})
Expand All @@ -44,8 +48,7 @@ test('when auth fails, send the user to login with a toast', async () => {
const request = await setupRequest()
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
}).catch((e) => e)
invariant(response instanceof Response, 'response should be a Response')
expect(response).toHaveRedirect('/login')
Expand All @@ -67,8 +70,7 @@ test('when a user is logged in, it creates the connection', async () => {
})
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
Expand Down Expand Up @@ -107,8 +109,7 @@ test(`when a user is logged in and has already connected, it doesn't do anything
})
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
Expand All @@ -126,8 +127,7 @@ test('when a user exists with the same email, create connection and make session
const request = await setupRequest({ code: githubUser.code })
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})

expect(response).toHaveRedirect('/')
Expand Down Expand Up @@ -174,8 +174,7 @@ test('gives an error if the account is already connected to another user', async
})
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})
expect(response).toHaveRedirect('/settings/profile/connections')
await expect(response).toSendToast(
Expand All @@ -201,8 +200,7 @@ test('if a user is not logged in, but the connection exists, make a session', as
const request = await setupRequest({ code: githubUser.code })
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})
expect(response).toHaveRedirect('/')
await expect(response).toHaveSessionForUser(userId)
Expand All @@ -229,8 +227,7 @@ test('if a user is not logged in, but the connection exists and they have enable
const request = await setupRequest({ code: githubUser.code })
const response = await loader({
request,
params: PARAMS,
context: {} as AppLoadContext,
...LOADER_ARGS_BASE,
})
const searchParams = new URLSearchParams({
type: twoFAVerificationType,
Expand Down
24 changes: 20 additions & 4 deletions app/utils/cache.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@ const CACHE_DATABASE_PATH = process.env.CACHE_DATABASE_PATH
const cacheDb = remember('cacheDb', createDatabase)

function createDatabase(tryAgain = true): DatabaseSync {
const parentDir = path.dirname(CACHE_DATABASE_PATH)
const databasePath = CACHE_DATABASE_PATH
if (!databasePath) {
throw new Error('CACHE_DATABASE_PATH is not set')
}

const parentDir = path.dirname(databasePath)
fs.mkdirSync(parentDir, { recursive: true })

const db = new DatabaseSync(CACHE_DATABASE_PATH)
const db = new DatabaseSync(databasePath)
const { currentIsPrimary } = getInstanceInfoSync()
if (!currentIsPrimary) return db

Expand All @@ -41,10 +46,21 @@ function createDatabase(tryAgain = true): DatabaseSync {
)
`)
} catch (error: unknown) {
fs.unlinkSync(CACHE_DATABASE_PATH)
try {
fs.rmSync(databasePath, { force: true })
} catch (unlinkError) {
if (
typeof unlinkError !== 'object' ||
unlinkError === null ||
!('code' in unlinkError) ||
unlinkError.code !== 'ENOENT'
) {
throw unlinkError
}
}
if (tryAgain) {
console.error(
`Error creating cache database, deleting the file at "${CACHE_DATABASE_PATH}" and trying again...`,
`Error creating cache database, deleting the file at "${databasePath}" and trying again...`,
)
return createDatabase(false)
}
Expand Down
2 changes: 1 addition & 1 deletion app/utils/storage.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function uploadToStorage(file: File | FileUpload, key: string) {
const uploadResponse = await fetch(url, {
method: 'PUT',
headers,
body: file instanceof File ? file : file.stream(),
body: file instanceof File ? file : (file as FileUpload).stream(),
})

if (!uploadResponse.ok) {
Expand Down
13 changes: 6 additions & 7 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ is a precondition for a "Mock GitHub server" to be installed (with the help of
[module](../tests/mocks/github.ts) for more details and pay attention to how the
calls to `https://github.com/login/oauth/access_token` are being intercepted.
But once deployed to an environment where `process.env.MOCKS` is not set to
`'true'` (see how this is done when launching the
[server](../server/index.ts) and checked in the
[entrypoint](../index.ts)), or even when developing _locally_ but not setting
`GITHUB_CLIENT_ID` to `MOCK_...`, the requests will actually reach the GitHub
auth server. This is where you will want to have a GitHub OAuth application
properly set up, otherwise the logging in with GitHub will fail and a
corresponding toast will appear on the screen.
`'true'` (see how this is done when launching the [server](../server/index.ts)
and checked in the [entrypoint](../index.ts)), or even when developing _locally_
but not setting `GITHUB_CLIENT_ID` to `MOCK_...`, the requests will actually
reach the GitHub auth server. This is where you will want to have a GitHub OAuth
application properly set up, otherwise the logging in with GitHub will fail and
a corresponding toast will appear on the screen.

To set up a real OAuth application, log in to GitHub, go to
`Settings -> Developer settings -> OAuth Apps`, and hit the
Expand Down
1 change: 0 additions & 1 deletion docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ You've got a few options:
re-generating the migration after fixing the error.
3. If you do care about the data and don't have a backup, you can follow these
steps:

1. Comment out the
[`exec` section from `litefs.yml` file](https://github.com/epicweb-dev/epic-stack/blob/main/other/litefs.yml#L31-L37).

Expand Down
9 changes: 5 additions & 4 deletions docs/decisions/031-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ and manually modify.
Despite the magic of Path aliases, they are actually a standard `package.json`
supported feature. Sort of.
[The `"imports"` field](https://nodejs.org/api/packages.html#imports) in
`package.json` allows you to configure aliases for your imports.
TypeScript also uses this for its own Path aliases since version 5.4
so you get autocomplete and type checking for your imports.
`package.json` allows you to configure aliases for your imports. TypeScript also
uses this for its own Path aliases since version 5.4 so you get autocomplete and
type checking for your imports.

By using the `"imports"` field, you don't have to do any special configuration
for `vitest` or `eslint` to be able to resolve imports. They just resolve them
Expand All @@ -44,7 +44,8 @@ again it's just a matter of familiarity. So it's no big deal.

## Decision

We're going to configure `"imports"` in the `package.json` to use path aliases for imports.
We're going to configure `"imports"` in the `package.json` to use path aliases
for imports.

We'll set it to `"#*": "./*"` which will allow us to import anything in the root
of the repo with `#<dirname>/<filepath>`.
Expand Down
12 changes: 0 additions & 12 deletions docs/decisions/039-passkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ username/password and OAuth providers. While these methods are widely used, they
come with various security challenges:

1. Password-based authentication:

- Users often reuse passwords across services
- Passwords can be phished or stolen
- Password management is a burden for users
Expand Down Expand Up @@ -39,14 +38,12 @@ using:
The authentication flow works as follows:

1. Registration:

- Server generates a challenge and sends registration options
- Client creates a new key pair and signs the challenge with the private key
- Public key and metadata are sent to the server for storage
- Private key remains securely stored in the authenticator

2. Authentication:

- Server generates a new challenge
- Client signs it with the stored private key
- Server verifies the signature using the stored public key
Expand All @@ -64,19 +61,16 @@ While passkeys represent the future of authentication, we maintain support for
password and OAuth authentication because:

1. Adoption and Transition:

- Passkey support is still rolling out across platforms and browsers
- Users need time to become comfortable with the new technology
- Organizations may have existing requirements for specific auth methods

2. Fallback Options:

- Some users may not have compatible devices
- Enterprise environments might restrict biometric authentication
- Backup authentication methods provide reliability

3. User Choice:

- Different users have different security/convenience preferences
- Some scenarios may require specific authentication types
- Supporting multiple methods maximizes accessibility
Expand Down Expand Up @@ -112,43 +106,37 @@ We chose SimpleWebAuthn because:
### Positive:

1. Enhanced Security for Users:

- Phishing-resistant authentication adds protection against common attacks
- Hardware-backed security provides stronger guarantees than passwords alone
- Biometric authentication reduces risk of credential sharing

2. Improved User Experience Options:

- Users can choose between password, OAuth, or passkey based on their needs
- Native biometric flows provide fast and familiar authentication
- Password manager integration enables seamless cross-device access
- Multiple authentication methods increase accessibility

3. Future-Proofing Authentication:

- Adoption of web standard
- Gradual transition path as passkey support grows
- Meeting evolving security best practices

### Negative:

1. Implementation Complexity:

- WebAuthn is a complex specification
- Need to handle various device capabilities
- Must maintain backward compatibility
- Need to maintain password-based auth as fallback

2. User Education:

- New technology requires user education
- Some users may be hesitant to adopt
- Need clear documentation and UI guidance

### Neutral:

1. Data Storage:

- New database model for passkeys
- Additional storage requirements per user
- Migration path for existing users
Expand Down
3 changes: 0 additions & 3 deletions docs/decisions/043-pwnedpasswords.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@ However, we wanted to implement this in a way that:
We will integrate the HaveIBeenPwned Password API with the following approach:

1. **Progressive Enhancement**

- The password check is implemented as a non-blocking enhancement
- If the check fails or times out (>1s), we allow the password
- This ensures users can still set passwords even if the service is
unavailable

2. **Development Experience**

- The API calls are mocked during development and testing using MSW (Mock
Service Worker)
- This prevents unnecessary API calls during development
- Allows for consistent testing behavior
- Follows our pattern of mocking external services

3. **Error Handling**

- Timeout after 1 second to prevent blocking users
- Graceful fallback if the service is unavailable
- Warning logs for monitoring service health
Expand Down
68 changes: 33 additions & 35 deletions docs/decisions/044-rr-devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,33 @@ Status: accepted

## Context

Epic Stack uses React Router for routing. React Router is a powerful
library, but it can be difficult to debug and visualize the routing
in your application. This is especially true when you have a complex
routing structure with nested routes, dynamic routes, and you rely
on data functions like loaders and actions, which the Epic Stack does.

It is also hard to know which routes are currently active
(which ones are rendered) and if any if the loaders are triggered
when you expect them to be. This can lead to confusion and frustration
and the use of console.log statements to debug the routing in your
application.

This is where the React Router DevTools come in. The React
Router DevTools are a set of tools that do all of these things for you.

React Router has a set of DevTools that help debug and visualize the
routing in your application. The DevTools allow you to see the
current route information, including the current location, the matched
routes, and the route hierarchy. This can be very helpful when debugging
your applications. The DevTools also hook into your server-side by
wrapping loaders and actions, allowing you to get extensive
information about the data being loaded and the actions being dispatched.
Epic Stack uses React Router for routing. React Router is a powerful library,
but it can be difficult to debug and visualize the routing in your application.
This is especially true when you have a complex routing structure with nested
routes, dynamic routes, and you rely on data functions like loaders and actions,
which the Epic Stack does.

It is also hard to know which routes are currently active (which ones are
rendered) and if any if the loaders are triggered when you expect them to be.
This can lead to confusion and frustration and the use of console.log statements
to debug the routing in your application.

This is where the React Router DevTools come in. The React Router DevTools are a
set of tools that do all of these things for you.

React Router has a set of DevTools that help debug and visualize the routing in
your application. The DevTools allow you to see the current route information,
including the current location, the matched routes, and the route hierarchy.
This can be very helpful when debugging your applications. The DevTools also
hook into your server-side by wrapping loaders and actions, allowing you to get
extensive information about the data being loaded and the actions being
dispatched.

## Decision

We will add the React Router DevTools to the Epic Stack. The DevTools
will be added to the project as a development dependency. The DevTools
will be used in development mode only.
We will add the React Router DevTools to the Epic Stack. The DevTools will be
added to the project as a development dependency. The DevTools will be used in
development mode only.

The DevTools will be used to enhance the following:

Expand All @@ -45,19 +44,18 @@ The DevTools will be used to enhance the following:
6. See cache information returned via headers from your loaders
7. See which loaders/actions are triggered when you navigate to a route
8. and a lot more!


## Consequences

With the addition of the React Router DevTools, you will not have to rely on
console.log statements to debug your routing. The DevTools will provide you
with the tools to ship your applications faster and with more confidence.
console.log statements to debug your routing. The DevTools will provide you with
the tools to ship your applications faster and with more confidence.

The DevTools will also help you visualize the routing in your application,
which can be very helpful in understanding routing in general, and figuring
out if your routes are set up correctly.
The DevTools will also help you visualize the routing in your application, which
can be very helpful in understanding routing in general, and figuring out if
your routes are set up correctly.

They are not included in the production build by default, so you will not
have to worry about them being included in your production bundle.
They are only included in development mode, so you can use them without
any negative performance impact in production.
They are not included in the production build by default, so you will not have
to worry about them being included in your production bundle. They are only
included in development mode, so you can use them without any negative
performance impact in production.
Loading
Loading