Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ Run with `pnpm <script>`.
**Setup**
- `setup` — Full setup (install, hooks, gitleaks, osv, database)
- `setup:gitleaks`, `setup:osv` — Install Gitleaks, OSV scanner
- `setup:database` — Database tools
- `setup:database` — Database tools (Docker, Supabase CLI)
- `reset` — Local API database: Supabase reset + Drizzle migrations + seed (`pnpm --filter @repo/api reset`). See [apps/api/README.md](apps/api/README.md)

**Primary**
- `build` — Build packages and apps
- `dev` — Start dev (core, react, error, utils, api, web)
Expand Down
9 changes: 6 additions & 3 deletions apps/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Type-safe REST API built with Fastify & OpenAPI. Routes in `src/routes/` are the

Copy [`.env.defaults.example`](.env.defaults.example) to `.env` and set values (gitignored). Start database first (`pnpm db:start`), then `pnpm dev`. Uses Supabase CLI for PostgreSQL, or `PGLITE=true` for in-memory. Dev server at [http://localhost:3000](http://localhost:3000).

**Switching project_id:** If you change `project_id` in `supabase/config.toml` (e.g. after a rebrand), run `pnpm db:stop` before `pnpm db:start`—only one Supabase instance runs per host.

## Vercel

Uses `framework: "fastify"` in vercel.json. Vercel auto-detects `server.ts` as the entrypoint. PostgreSQL migrations run at build time; PGLite migrations run at runtime.
Expand All @@ -30,14 +32,15 @@ Copy `.env.test.example` to `.env.test` (gitignored) for unit tests. Vitest load
- `pnpm test:e2e:debug` — Debug E2E tests
- `pnpm checktypes` — Type-check
- `pnpm db:start` — Start Supabase (local)
- `pnpm db:stop` — Stop Supabase
- `pnpm db:reset` — Reset Supabase database (recreates from scratch)
- `pnpm db:reset-and-migrate` — Reset DB then run Drizzle migrations
- `pnpm db:stop` — Stop Supabase (run before switching to another project’s Supabase)
- `pnpm reset` — From repo root: `pnpm --filter @repo/api reset`. From `apps/api`: Supabase DB reset, then Drizzle migrations (`scripts/migrate.ts`), then seed (`scripts/seed.ts`) with local `DATABASE_URL` + `RUN_PG_MIGRATE=true`. `[db.seed]` / `seed.sql` unused (`supabase/config.toml`)
- `pnpm db:migrate` — Run migrations (skips when PGLITE=true; use `RUN_PG_MIGRATE=true` to force PostgreSQL)
- `pnpm db:generate` — Generate migrations from schema
- `pnpm db:push` — Push schema (dev only)
- `pnpm generate:openapi` — Regenerate OpenAPI spec

**Database:** `drizzle.config.ts` defines schema glob (`src/db/schema/tables/*.ts`) and migration output (`src/db/migrations`). `scripts/migrate.ts` runs the Drizzle migrator against PostgreSQL (or skips at build time when using PGLite—see `src/db/migrate.ts` at runtime). `pnpm reset` runs `scripts/seed.ts` (`runSeed`) after migrations; `pnpm db:migrate` alone does not.

## Links

- [Environment setup](https://basilic-docs.vercel.app/docs/development) — Env vars, `DATABASE_URL`, `PGLITE`
Expand Down
1 change: 1 addition & 0 deletions apps/api/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** Migrations + schema. Local data seed: `scripts/seed.ts` (via `pnpm reset` in apps/api). */
import 'dotenv/config'
import { defineConfig } from 'drizzle-kit'

Expand Down
3 changes: 1 addition & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
"generate:openapi": "node --import tsx scripts/generate-openapi.ts",
"db:start": "supabase start",
"db:stop": "supabase stop",
"db:reset": "supabase db reset",
"db:reset-and-migrate": "pnpm db:reset && RUN_PG_MIGRATE=true DATABASE_URL=\"postgresql://postgres:postgres@127.0.0.1:54322/postgres\" pnpm db:migrate",
"reset": "supabase db reset && RUN_PG_MIGRATE=true DATABASE_URL=\"postgresql://postgres:postgres@127.0.0.1:54322/postgres\" sh -c 'pnpm db:migrate && node --import tsx scripts/seed.ts'",
Comment thread
gaboesquivel marked this conversation as resolved.
"db:status": "supabase status",
"db:migrate": "node --import tsx scripts/migrate.ts",
"db:generate": "NODE_OPTIONS='--import tsx' drizzle-kit generate",
Expand Down
8 changes: 4 additions & 4 deletions apps/api/scripts/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async function initMigrationsTrackingWhenTablesExist(

try {
// --- PGLite vs PostgreSQL ---
// Allow explicit override: RUN_PG_MIGRATE=true forces PostgreSQL path (e.g. after db:reset)
// Allow explicit override: RUN_PG_MIGRATE=true forces PostgreSQL path (e.g. after pnpm reset)
const forcePg = process.env.RUN_PG_MIGRATE === 'true'
const shouldUsePGLite = !forcePg && (env.PGLITE === true || env.NODE_ENV === 'test')

Expand Down Expand Up @@ -102,7 +102,7 @@ try {
migrationsTableExists = migrationsTableCheck.rows[0]?.exists ?? false

if (!migrationsTableExists) {
// Tables that must exist to consider this a "seeded" DB (matches initial schema)
// Tables that must exist to treat the DB as already matching the initial schema (migration bootstrap)
const requiredTables = ['users', 'sessions', 'verification', 'account', 'wallet_identities']
const tableCheckPromises = requiredTables.map(tableName =>
pool.query(
Expand Down Expand Up @@ -162,15 +162,15 @@ try {
} else if (isTableExistsError) {
logger.warn(
{ context: 'migrate', err: migrationError },
'Migration failed due to existing tables. Tables appear to match schema. For clean state, run: pnpm --filter @repo/api db:reset',
'Migration failed due to existing tables. Tables appear to match schema. For clean state, run: pnpm --filter @repo/api reset',
)
// In development/build, allow this to pass if tables exist and match schema
// In production, this should fail to ensure proper migration tracking
if (process.env.NODE_ENV === 'production') throw migrationError

logger.info(
{ context: 'migrate' },
'Allowing build to continue - tables exist and appear to match expected schema. Consider running db:reset for clean migration state.',
'Allowing build to continue - tables exist and appear to match expected schema. Consider running pnpm reset for clean migration state.',
)
} else {
logger.error({ context: 'migrate', err: migrationError }, 'Migration failed')
Expand Down
61 changes: 61 additions & 0 deletions apps/api/scripts/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env node
/**
* Data seed after a local Supabase reset. Invoked only from `pnpm reset` (apps/api or repo root via filter).
* (not from `pnpm db:migrate` / `pnpm build`).
*
* Add idempotent inserts here (`onConflictDoNothing()` / upserts). Uses the same
* PostgreSQL vs PGLite rules as `scripts/migrate.ts` (`RUN_PG_MIGRATE`, `DATABASE_URL`).
*/
import 'dotenv/config'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { logger } from '@repo/utils/logger/server'
import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
import * as schema from '../src/db/schema/index.js'
import { env } from '../src/lib/env.js'

const scriptFile = fileURLToPath(import.meta.url)

async function applySeed(_db: NodePgDatabase<typeof schema>): Promise<void> {
// Reference / dev rows only — extend with db.insert(...).onConflictDoNothing(), etc.
}

export async function runSeed(): Promise<void> {
const forcePg = process.env.RUN_PG_MIGRATE === 'true'
const shouldUsePGLite = !forcePg && (env.PGLITE === true || env.NODE_ENV === 'test')

if (shouldUsePGLite) {
logger.info({ context: 'seed' }, 'PGLite: skipping data seed')
return
}

if (!env.DATABASE_URL) throw new Error('DATABASE_URL is required when PGLITE is false')

const pool = new Pool({ connectionString: env.DATABASE_URL })
const db = drizzle(pool, { schema })
try {
await applySeed(db)
logger.info({ context: 'seed' }, 'Seed completed')
} finally {
await pool.end()
}
}

function isMainModule(): boolean {
const entry = process.argv[1]
if (!entry) return false
return resolve(entry) === scriptFile
}

async function main(): Promise<void> {
try {
await runSeed()
process.exit(0)
} catch (err) {
logger.error({ context: 'seed', err }, 'Seed failed')
process.exit(1)
}
}

if (isMainModule()) void main()
8 changes: 3 additions & 5 deletions apps/api/supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# https://supabase.com/docs/guides/local-development/cli/config
# A string used to distinguish different Supabase projects on the same host. Defaults to the
# working directory name when running `supabase init`.
project_id = "basilic-fastify"
project_id = "basilic-api"

[api]
enabled = true
Expand Down Expand Up @@ -59,10 +59,8 @@ schema_paths = []

[db.seed]
# If enabled, seeds the database after migrations during a db reset.
enabled = true
# Specifies an ordered list of seed files to load during db reset.
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
sql_paths = ["./seed.sql"]
# Disabled — schema from Drizzle (scripts/migrate.ts); optional data rows from scripts/seed.ts via pnpm reset (apps/api)
enabled = false

[db.network_restrictions]
# Enable management of network restrictions.
Expand Down
2 changes: 2 additions & 0 deletions apps/docu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Starts docs site at [http://localhost:3002](http://localhost:3002).
- `pnpm start` - Production server
- `pnpm checktypes` - Type check MDX and TypeScript

Monorepo root (for local API database): `pnpm reset` — see `apps/api/README.md`.

## Content

Documentation content in `content/docs/`:
Expand Down
12 changes: 11 additions & 1 deletion apps/docu/content/docs/adrs/008-database.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,17 @@ All platforms use the same build command pattern:

- **Docker**: `RUN pnpm build` (runs migrations)
- **Railway/Fly.io/Render**: `build: pnpm build` (runs migrations)
- **Local dev**: PostgreSQL migrations run at build time (via `pnpm dev` → `pnpm db:migrate`), PGLite migrations run at runtime
- **Local dev (PostgreSQL)**: Migrations run when you **build** the API (`pnpm build` / `pnpm --filter @repo/api build`), not when you run `pnpm dev` alone. To wipe local Supabase and re-apply schema + seed, use **`pnpm reset`** (repo root) or **`pnpm reset`** in `apps/api`. **PGLite**: migrations still run at runtime

### Local database reset (Supabase + Drizzle)

For local Postgres via Supabase CLI (`project_id: basilic-api` in `apps/api/supabase/config.toml`), use **`pnpm reset`** from the repo root (runs `pnpm --filter @repo/api reset`) or **`pnpm reset`** from `apps/api`. One command does:

1. **`supabase db reset`** — Drops and recreates the database. `[db.seed]` is off; no `seed.sql`.
2. **Drizzle migrations** — `scripts/migrate.ts` via `pnpm db:migrate` with `RUN_PG_MIGRATE=true` and the local Supabase `DATABASE_URL`.
3. **Data seed** — `scripts/seed.ts` (`runSeed`). Add idempotent inserts as needed. Runs only as part of **`pnpm reset`**, not on `pnpm db:migrate` or `pnpm build` alone. See `apps/api/README.md`.

Run `pnpm db:stop` before switching to another project's Supabase instance (e.g. different repo); only one Supabase runs per host.

### Key Benefits

Expand Down
2 changes: 2 additions & 0 deletions apps/docu/content/docs/architecture/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ TypeScript is generated by `@hey-api/openapi-ts`. Other languages can use standa

The API layer uses Drizzle for queries and `drizzle-kit` for migrations. Migration strategy depends on the database runtime; see [ADR 008: Database Platform & Strategy](/docs/adrs/008-database#migration-strategy).

For local Supabase: **`pnpm reset`** from the monorepo root runs Supabase DB reset, Drizzle migrations, and `scripts/seed.ts` (details in ADR 008).

## Security

- **Headers**: X-Content-Type-Options, X-Frame-Options, CSP, HSTS
Expand Down
2 changes: 1 addition & 1 deletion apps/docu/content/docs/development/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,4 @@ flowchart TB

**Optional Setup:**

For database features, you'll need PostgreSQL. See [Installation](/docs/development).
For database features, use local PostgreSQL via Supabase: `pnpm --filter @repo/api db:start`, configure `apps/api/.env`, then apply schema and optional seed with **`pnpm reset`** from the repo root (Supabase reset + Drizzle migrate + `scripts/seed.ts`). See [ADR 008: Database](/docs/adrs/008-database) and `apps/api/README.md` in the monorepo.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"setup:gitleaks": "node scripts/setup-gitleaks.mjs",
"setup:osv": "node scripts/setup-osv-scanner.mjs",
"setup:database": "node scripts/setup-database.mjs",
"reset": "pnpm --filter @repo/api reset",
"update-deps": "pnpm self-update && pnpm update --latest --recursive"
},
"devDependencies": {
Expand Down Expand Up @@ -61,6 +62,13 @@
],
"overrides": {
"lightningcss": "1.30.1",
"flatted@<3.4.2": "3.4.2",
"h3@<1.15.9": "1.15.9",
"socket.io-parser@4.2.5": "4.2.6",
"undici@5.28.4": "6.24.0",
"undici@6.23.0": "6.24.0",
"undici@7.22.0": "7.24.0",
"next@16.1.6": "16.1.7",
"tar@<7.5.11": "7.5.11",
"basic-ftp@<5.2.0": "5.2.0",
"bn.js@4.12.2": "4.12.3",
Expand Down
1 change: 1 addition & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.2.14",
"@typescript/native-preview": "7.0.0-dev.20260213.1",
"@vitejs/plugin-react": "^5.1.2",
"eslint": "^9.0.0",
"jsdom": "^28.0.0",
"react": "^19.2.4",
Expand Down
15 changes: 15 additions & 0 deletions packages/react/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import { realpathSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vitest/config'

const configDir = dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)

const reactPackageRoot = realpathSync(dirname(require.resolve('react/package.json')))
const reactDomPackageRoot = realpathSync(dirname(require.resolve('react-dom/package.json')))
const reactDomClientEntry = realpathSync(require.resolve('react-dom/client'))

export default defineConfig({
plugins: [react()],
test: {
include: ['src/**/*.{test,spec}.{ts,tsx}'],
globals: true,
environment: 'jsdom',
setupFiles: ['./vitest.setup.ts'],
},
resolve: {
// pnpm: keep symlinked package paths so react-dom and @tanstack/react-query share one react instance
preserveSymlinks: true,
dedupe: ['react', 'react-dom'],
alias: {
'@repo/core': resolve(configDir, '../core/src/index.ts'),
'@repo/ui': resolve(configDir, '../ui/src'),
'@repo/utils': resolve(configDir, '../utils/src'),
react: reactPackageRoot,
'react-dom': reactDomPackageRoot,
'react-dom/client': reactDomClientEntry,
},
},
})
Loading
Loading