diff --git a/README.md b/README.md index 3712282..3753019 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ OpenReader WebUI is an open source text to speech document reader web app built | `API_KEY` | Default TTS API key | `none` or your provider key | | `BETTER_AUTH_URL` | Enables auth when set with `BETTER_AUTH_SECRET` | External URL for this app, e.g. `http://localhost:3003` or `https://reader.example.com` | | `BETTER_AUTH_SECRET` | Enables auth when set with `BETTER_AUTH_URL` | Generate with `openssl rand -base64 32` | - | `POSTGRES_URL` | Use Postgres for auth storage instead of SQLite | If set, run migrations manually (see note above) | + | `POSTGRES_URL` | Use Postgres for auth storage instead of SQLite | If set, startup migrations target Postgres | | `GITHUB_CLIENT_ID` | Optional GitHub OAuth sign-in | Requires `GITHUB_CLIENT_SECRET` | | `GITHUB_CLIENT_SECRET` | Optional GitHub OAuth sign-in | Requires `GITHUB_CLIENT_ID` | @@ -263,15 +263,20 @@ Optionally required for different features: - **Production / Docker**: Migrations run automatically on startup via `pnpm start`. - **Development**: Run explicitly: - ```bash - pnpm migrate - ``` + ```bash + pnpm migrate + ``` - > Note: If you set `POSTGRES_URL` in `.env`, migrations will target Postgres instead of local SQLite. - > - > Manual Drizzle Kit runs: - > - SQLite: `npx drizzle-kit migrate --config drizzle.config.sqlite.ts` - > - Postgres: `npx drizzle-kit migrate --config drizzle.config.pg.ts` + > Note: If you set `POSTGRES_URL` in `.env`, migrations will target Postgres instead of local SQLite. + > + > Generating migrations (contributors): + > - `pnpm generate` generates migrations for **both** SQLite and Postgres (requires `POSTGRES_URL`). + > + > Manual Drizzle Kit runs: + > - SQLite migrate: `npx drizzle-kit migrate --config drizzle.config.sqlite.ts` + > - Postgres migrate: `npx drizzle-kit migrate --config drizzle.config.pg.ts` + > - SQLite generate: `npx drizzle-kit generate --config drizzle.config.sqlite.ts` + > - Postgres generate: `npx drizzle-kit generate --config drizzle.config.pg.ts` 5. Start the development server: diff --git a/drizzle/scripts/generate.mjs b/drizzle/scripts/generate.mjs new file mode 100644 index 0000000..448e8e6 --- /dev/null +++ b/drizzle/scripts/generate.mjs @@ -0,0 +1,49 @@ +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import * as dotenv from 'dotenv'; + +function loadEnvFiles() { + // Approximate Next.js behavior enough for server-side scripts. + // Load .env first, then .env.local (local overrides). + const cwd = process.cwd(); + const envPath = path.join(cwd, '.env'); + const envLocalPath = path.join(cwd, '.env.local'); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + } + if (fs.existsSync(envLocalPath)) { + dotenv.config({ path: envLocalPath, override: true }); + } +} + +loadEnvFiles(); + +const authEnabled = Boolean(process.env.BETTER_AUTH_SECRET && process.env.BETTER_AUTH_URL); + +if (!authEnabled) { + // When auth is disabled, the app must not touch sqlite/postgres at all. + console.log('[generate] Skipping (auth disabled). Missing BETTER_AUTH_SECRET and/or BETTER_AUTH_URL.'); + process.exit(0); +} + +if (!process.env.POSTGRES_URL) { + console.error('[generate] POSTGRES_URL is required to generate postgres migrations.'); + process.exit(1); +} + +const extraArgs = process.argv.slice(2); + +for (const configFile of ['drizzle.config.sqlite.ts', 'drizzle.config.pg.ts']) { + const result = spawnSync('drizzle-kit', ['generate', '--config', configFile, ...extraArgs], { + stdio: 'inherit', + env: process.env, + }); + + if ((result.status ?? 1) !== 0) { + process.exit(result.status ?? 1); + } +} + +process.exit(0); diff --git a/scripts/migrate-if-auth.mjs b/drizzle/scripts/migrate-if-auth.mjs similarity index 100% rename from scripts/migrate-if-auth.mjs rename to drizzle/scripts/migrate-if-auth.mjs diff --git a/package.json b/package.json index 10be184..aa9db01 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "scripts": { "dev": "next dev --turbopack -p 3003", "build": "next build", - "start": "node scripts/migrate-if-auth.mjs && next start -p 3003", + "start": "node drizzle/scripts/migrate-if-auth.mjs && next start -p 3003", "lint": "next lint", "test": "playwright test", - "migrate": "node scripts/migrate-if-auth.mjs", - "migrate:force": "node scripts/migrate.mjs" + "migrate": "node drizzle/scripts/migrate-if-auth.mjs", + "generate": "node drizzle/scripts/generate.mjs" }, "dependencies": { "@headlessui/react": "^2.2.9", diff --git a/scripts/migrate.mjs b/scripts/migrate.mjs deleted file mode 100644 index 1e0914a..0000000 --- a/scripts/migrate.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { spawnSync } from 'node:child_process'; -import path from 'node:path'; -import fs from 'node:fs'; -import * as dotenv from 'dotenv'; - -function loadEnvFiles() { - // Approximate Next.js behavior enough for server-side scripts. - // Load .env first, then .env.local (local overrides). - const cwd = process.cwd(); - const envPath = path.join(cwd, '.env'); - const envLocalPath = path.join(cwd, '.env.local'); - - if (fs.existsSync(envPath)) { - dotenv.config({ path: envPath }); - } - if (fs.existsSync(envLocalPath)) { - dotenv.config({ path: envLocalPath, override: true }); - } -} - -loadEnvFiles(); - -const extraArgs = process.argv.slice(2); -const hasConfigArg = extraArgs.includes('--config'); -const configFile = process.env.POSTGRES_URL ? 'drizzle.config.pg.ts' : 'drizzle.config.sqlite.ts'; -const configArgs = hasConfigArg ? [] : ['--config', configFile]; - -const result = spawnSync('drizzle-kit', ['migrate', ...configArgs, ...extraArgs], { - stdio: 'inherit', - env: process.env, -}); - -process.exit(result.status ?? 1);