Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
cf0f1fb
upgrade safe minor and patch dependencies
cursoragent Feb 12, 2026
efeacd6
upgrade jsdom to v28
cursoragent Feb 12, 2026
3cfde57
upgrade eslint to v10
cursoragent Feb 12, 2026
c12c09e
upgrade to zod v4 with conform v4 imports
cursoragent Feb 12, 2026
af3ab08
migrate prisma to v7 with sqlite adapter
cursoragent Feb 12, 2026
9addca1
stabilize profile photo e2e upload selector
cursoragent Feb 12, 2026
4d0b8eb
fix import order warning in remix init script
cursoragent Feb 12, 2026
5b0cb34
apply non-breaking npm audit transitive fixes
cursoragent Feb 12, 2026
d52f722
update prisma v7 seeding docs commands
cursoragent Feb 12, 2026
2e7d51b
remove deprecated prisma seed config and align docs
cursoragent Feb 12, 2026
76fe383
run prisma migrations before local e2e runs
cursoragent Feb 12, 2026
1111443
document prisma v7 config usage in database skill
cursoragent Feb 12, 2026
83fbe09
chore(deps): override hono to patched version
cursoragent Feb 12, 2026
489b22b
chore(deps): bump dotenv to latest
cursoragent Feb 12, 2026
234f6ff
chore(deps): remove redundant @types/glob
cursoragent Feb 12, 2026
a1074bf
chore(deps): override prisma-ast to patched transitive
cursoragent Feb 12, 2026
5c2515d
chore(deps): override lodash to patched transitive
cursoragent Feb 12, 2026
9fab772
chore(deps): remove npm overrides per preference
cursoragent Feb 12, 2026
a5bf8f2
chore(ci): upgrade github action dependencies
cursoragent Feb 12, 2026
9956aa7
chore(ci): pin github actions to latest releases
cursoragent Feb 12, 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
32 changes: 16 additions & 16 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2

- name: ⎔ Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.2.0
with:
node-version: 22

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
uses: bahmutov/npm-install@v1.11.2

- name: 🏄 Copy test env vars
run: cp .env.example .env
Expand All @@ -44,15 +44,15 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2

- name: ⎔ Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.2.0
with:
node-version: 22

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
uses: bahmutov/npm-install@v1.11.2

- name: 🏗 Build
run: npm run build
Expand All @@ -71,15 +71,15 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2

- name: ⎔ Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.2.0
with:
node-version: 22

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
uses: bahmutov/npm-install@v1.11.2

- name: 🏄 Copy test env vars
run: cp .env.example .env
Expand All @@ -96,18 +96,18 @@ jobs:
timeout-minutes: 60
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2

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

- name: ⎔ Setup node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.2.0
with:
node-version: 22

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
uses: bahmutov/npm-install@v1.11.2

- name: 📥 Install Playwright Browsers
run: npm run test:e2e:install
Expand All @@ -117,7 +117,7 @@ jobs:

- name: 🏦 Cache Database
id: db-cache
uses: actions/cache@v4
uses: actions/cache@v5.0.3
with:
path: prisma/data.db
key:
Expand All @@ -136,7 +136,7 @@ jobs:
run: npx playwright test

- name: 📊 Upload report
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6.0.0
if: always()
with:
name: playwright-report
Expand All @@ -150,7 +150,7 @@ jobs:
if: ${{ github.event_name == 'push' }}
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2
with:
fetch-depth: 50

Expand Down Expand Up @@ -197,7 +197,7 @@ jobs:
if: ${{ github.event_name == 'push' }}
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v6.0.2
with:
fetch-depth: '50'

Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/forgot-password.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import * as E from '@react-email/components'
import { data, redirect, Link, useFetcher } from 'react-router'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/login.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { startAuthentication } from '@simplewebauthn/browser'
import { useOptimistic, useState, useTransition } from 'react'
Expand Down
4 changes: 2 additions & 2 deletions app/routes/_auth/onboarding/$provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
useForm,
type SubmissionResult,
} from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import {
redirect,
data,
Expand Down Expand Up @@ -40,7 +40,7 @@ const SignupFormSchema = z.object({
username: UsernameSchema,
name: NameSchema,
agreeToTermsOfServiceAndPrivacyPolicy: z.boolean({
required_error: 'You must agree to the terms of service and privacy policy',
error: 'You must agree to the terms of service and privacy policy',
}),
remember: z.boolean().optional(),
redirectTo: z.string().optional(),
Expand Down
4 changes: 2 additions & 2 deletions app/routes/_auth/onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { data, redirect, Form, useSearchParams } from 'react-router'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { safeRedirect } from 'remix-utils/safe-redirect'
Expand Down Expand Up @@ -33,7 +33,7 @@ const SignupFormSchema = z
username: UsernameSchema,
name: NameSchema,
agreeToTermsOfServiceAndPrivacyPolicy: z.boolean({
required_error:
error:
'You must agree to the terms of service and privacy policy',
}),
remember: z.boolean().optional(),
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/reset-password.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { data, redirect, Form } from 'react-router'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import * as E from '@react-email/components'
import { data, redirect, Form, useSearchParams } from 'react-router'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/verify.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Submission } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { parseWithZod } from '@conform-to/zod/v4'
import { data } from 'react-router'
import { z } from 'zod'
import { handleVerification as handleChangeEmailVerification } from '#app/routes/settings/profile/change-email.server.tsx'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_auth/verify.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { Form, useSearchParams } from 'react-router'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/resources/theme-switch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useForm, getFormProps } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { parseWithZod } from '@conform-to/zod/v4'
import { invariantResponse } from '@epic-web/invariant'
import { data, redirect, useFetcher, useFetchers } from 'react-router'
import { ServerOnly } from 'remix-utils/server-only'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/settings/profile/change-email.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { data, redirect, Form } from 'react-router'
import { z } from 'zod'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/settings/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { invariantResponse } from '@epic-web/invariant'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { Img } from 'openimg/react'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/settings/profile/password.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { data, redirect, Form, Link } from 'react-router'
import { z } from 'zod'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/settings/profile/password_.create.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import { data, redirect, Form, Link } from 'react-router'
import { ErrorList, Field } from '#app/components/forms.tsx'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/settings/profile/photo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { invariantResponse } from '@epic-web/invariant'
import { parseFormData } from '@mjackson/form-data-parser'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
Expand Down
4 changes: 2 additions & 2 deletions app/routes/settings/profile/two-factor/verify.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
import * as QRCode from 'qrcode'
import { data, redirect, Form, useNavigation } from 'react-router'
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function action({ request }: Route.ActionArgs) {
const submission = await parseWithZod(formData, {
schema: () =>
ActionSchema.superRefine(async (data, ctx) => {
if (data.intent === 'cancel') return null
if (data.intent === 'cancel') return
const codeIsValid = await isCodeValid({
code: data.code,
type: twoFAVerifyVerificationType,
Expand Down
2 changes: 1 addition & 1 deletion app/routes/users/$username/notes/$noteId.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getFormProps, useForm } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { parseWithZod } from '@conform-to/zod/v4'
import { invariantResponse } from '@epic-web/invariant'
import { formatDistanceToNow } from 'date-fns'
import { Img } from 'openimg/react'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parseWithZod } from '@conform-to/zod'
import { parseWithZod } from '@conform-to/zod/v4'
import { parseFormData } from '@mjackson/form-data-parser'
import { createId as cuid } from '@paralleldrive/cuid2'
import { data, redirect, type ActionFunctionArgs } from 'react-router'
Expand Down
2 changes: 1 addition & 1 deletion app/routes/users/$username/notes/+shared/note-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useForm,
type FieldMetadata,
} from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod/v4'
import { Img } from 'openimg/react'
import { useState } from 'react'
import { Form } from 'react-router'
Expand Down
30 changes: 26 additions & 4 deletions app/routes/users/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { searchUsers } from '@prisma/client/sql'
import { Img } from 'openimg/react'
import { redirect, Link } from 'react-router'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
Expand All @@ -14,9 +13,32 @@ export async function loader({ request }: Route.LoaderArgs) {
return redirect('/users')
}

const like = `%${searchTerm ?? ''}%`
const users = await prisma.$queryRawTyped(searchUsers(like))
return { status: 'idle', users } as const
const users = await prisma.user.findMany({
where: {
OR: [
{ username: { contains: searchTerm ?? '' } },
{ name: { contains: searchTerm ?? '' } },
],
},
select: {
id: true,
username: true,
name: true,
image: { select: { id: true, objectKey: true } },
},
orderBy: { updatedAt: 'desc' },
take: 50,
})
return {
status: 'idle',
users: users.map((user) => ({
id: user.id,
username: user.username,
name: user.name,
imageId: user.image?.id ?? null,
imageObjectKey: user.image?.objectKey ?? null,
})),
} as const
}

export default function UsersRoute({ loaderData }: Route.ComponentProps) {
Expand Down
9 changes: 9 additions & 0 deletions app/utils/db.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { styleText } from 'node:util'
import { remember } from '@epic-web/remember'
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
// Changed import due to issue: https://github.com/remix-run/react-router/pull/12644
import { PrismaClient } from '@prisma/client/index.js'

Expand All @@ -10,7 +11,15 @@ export const prisma = remember('prisma', () => {
// Feel free to change this log threshold to something that makes sense for you
const logThreshold = 20

const databaseUrl = process.env.DATABASE_URL
if (!databaseUrl) {
throw new Error('DATABASE_URL is not set')
}
const [sanitizedDatabaseUrl] = databaseUrl.split('?')
const adapter = new PrismaBetterSqlite3({ url: sanitizedDatabaseUrl })

const client = new PrismaClient({
adapter,
log: [
{ level: 'query', emit: 'event' },
{ level: 'error', emit: 'stdout' },
Expand Down
8 changes: 4 additions & 4 deletions app/utils/user-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const USERNAME_MIN_LENGTH = 3
export const USERNAME_MAX_LENGTH = 20

export const UsernameSchema = z
.string({ required_error: 'Username is required' })
.string({ error: 'Username is required' })
.min(USERNAME_MIN_LENGTH, { message: 'Username is too short' })
.max(USERNAME_MAX_LENGTH, { message: 'Username is too long' })
.regex(/^[a-zA-Z0-9_]+$/, {
Expand All @@ -14,7 +14,7 @@ export const UsernameSchema = z
.transform((value) => value.toLowerCase())

export const PasswordSchema = z
.string({ required_error: 'Password is required' })
.string({ error: 'Password is required' })
.min(6, { message: 'Password is too short' })
// NOTE: bcrypt has a limit of 72 bytes (which should be plenty long)
// https://github.com/epicweb-dev/epic-stack/issues/918
Expand All @@ -23,12 +23,12 @@ export const PasswordSchema = z
})

export const NameSchema = z
.string({ required_error: 'Name is required' })
.string({ error: 'Name is required' })
.min(3, { message: 'Name is too short' })
.max(40, { message: 'Name is too long' })

export const EmailSchema = z
.string({ required_error: 'Email is required' })
.string({ error: 'Email is required' })
.email({ message: 'Email is invalid' })
.min(3, { message: 'Email is too short' })
.max(100, { message: 'Email is too long' })
Expand Down
4 changes: 2 additions & 2 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ if what you need to seed is a lot of data), so here's an easy way to help out:
Then modify that file to create the data you want to seed.
1. Create a temporary database file to seed the data into.
```sh
DATABASE_URL=file:./seed.local.db npx prisma migrate reset --skip-seed --force
DATABASE_URL=file:./seed.local.db npx prisma migrate reset --force
```
1. Run the custom seed script locally to generate the data you want to seed.
```sh
Expand All @@ -186,7 +186,7 @@ If your app has already applied all migrations, then the changes to the
you can run the following command to apply the migration:

```sh nonumber
fly ssh console -C "npx prisma migrate reset --skip-seed --force" --app [YOUR_APP_NAME]
fly ssh console -C "npx prisma migrate reset --force" --app [YOUR_APP_NAME]
```

> **WARNING**: This will reset your database and apply all migrations. Continue
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ set SKIP_SETUP=true && set SKIP_FORMAT=true && set SKIP_DEPLOYMENT=true && npx e
- Seed database:

```sh
npx prisma@6 db seed
npx prisma migrate reset --force
```

- Start dev server:
Expand Down
Loading