diff --git a/packages/bootstrap/src/cli.mjs b/packages/bootstrap/src/cli.mjs index 887ecd7..04442d3 100644 --- a/packages/bootstrap/src/cli.mjs +++ b/packages/bootstrap/src/cli.mjs @@ -21,10 +21,26 @@ import { } from './embedded-seaweedfs.mjs'; import { resolveEmbeddedWorkerLaunch } from './embedded-worker.mjs'; +function findWorkspaceRoot(startDir = process.cwd()) { + let dir = startDir; + while (true) { + if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) { + return dir; + } + const parent = path.dirname(dir); + if (parent === dir) { + break; + } + dir = parent; + } + return startDir; +} + +const workspaceRoot = findWorkspaceRoot(process.cwd()); + function loadEnvFiles() { - const cwd = process.cwd(); - const envPath = path.join(cwd, '.env'); - const envLocalPath = path.join(cwd, '.env.local'); + const envPath = path.join(workspaceRoot, '.env'); + const envLocalPath = path.join(workspaceRoot, '.env.local'); if (fs.existsSync(envPath)) { dotenv.config({ path: envPath }); @@ -111,7 +127,7 @@ function spawnMainCommand(command, env) { async function runDbMigrations(env) { console.log('Running database migrations...'); - await runMigrations({ cwd: process.cwd(), env }); + await runMigrations({ cwd: workspaceRoot, env }); } function runStorageMigrations(env) { @@ -278,7 +294,7 @@ async function main() { } if (useEmbeddedWeed) { - runtimeEnv.WEED_MINI_DIR = withDefault(runtimeEnv.WEED_MINI_DIR, 'docstore/seaweedfs'); + runtimeEnv.WEED_MINI_DIR = withDefault(runtimeEnv.WEED_MINI_DIR, path.join(workspaceRoot, 'docstore/seaweedfs')); runtimeEnv.WEED_MINI_WAIT_SEC = withDefault(runtimeEnv.WEED_MINI_WAIT_SEC, '20'); runtimeEnv.S3_BUCKET = withDefault(runtimeEnv.S3_BUCKET, 'openreader-documents'); runtimeEnv.S3_REGION = withDefault(runtimeEnv.S3_REGION, 'us-east-1'); @@ -371,7 +387,7 @@ async function main() { runtimeEnv.COMPUTE_WORKER_HOST = withDefault(runtimeEnv.COMPUTE_WORKER_HOST, '127.0.0.1'); runtimeEnv.COMPUTE_NATS_REPLICAS = withDefault(runtimeEnv.COMPUTE_NATS_REPLICAS, '1'); - const natsStoreDir = withDefault(runtimeEnv.EMBEDDED_NATS_STORE_DIR, 'docstore/nats/jetstream'); + const natsStoreDir = withDefault(runtimeEnv.EMBEDDED_NATS_STORE_DIR, path.join(workspaceRoot, 'docstore/nats/jetstream')); fs.mkdirSync(natsStoreDir, { recursive: true }); console.log(`Starting embedded nats-server on 127.0.0.1:${embeddedNatsPort}...`); diff --git a/packages/bootstrap/src/storage-migration.mjs b/packages/bootstrap/src/storage-migration.mjs index e2f0c7d..53f159d 100644 --- a/packages/bootstrap/src/storage-migration.mjs +++ b/packages/bootstrap/src/storage-migration.mjs @@ -17,7 +17,23 @@ const { Pool } = require('pg'); const BetterSqlite3 = require('better-sqlite3'); const ffmpegStatic = require('ffmpeg-static'); -const DOCSTORE_DIR = path.join(process.cwd(), 'docstore'); +function findWorkspaceRoot(startDir = process.cwd()) { + let dir = startDir; + while (true) { + if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) { + return dir; + } + const parent = path.dirname(dir); + if (parent === dir) { + break; + } + dir = parent; + } + return startDir; +} + +const workspaceRoot = findWorkspaceRoot(process.cwd()); +const DOCSTORE_DIR = path.join(workspaceRoot, 'docstore'); const DOCUMENTS_V1_DIR = path.join(DOCSTORE_DIR, 'documents_v1'); const AUDIOBOOKS_V1_DIR = path.join(DOCSTORE_DIR, 'audiobooks_v1'); const UNCLAIMED_USER_ID = 'unclaimed'; @@ -27,10 +43,15 @@ const SAFE_AUDIOBOOK_ID_REGEX = /^[a-zA-Z0-9._-]{1,128}$/; const DOCUMENT_ID_REGEX = /^[a-f0-9]{64}$/i; function loadEnvFiles() { - const envPath = path.join(process.cwd(), '.env'); - const envLocalPath = path.join(process.cwd(), '.env.local'); - if (fs.existsSync(envPath)) dotenv.config({ path: envPath }); - if (fs.existsSync(envLocalPath)) dotenv.config({ path: envLocalPath, override: true }); + const envPath = path.join(workspaceRoot, '.env'); + const envLocalPath = path.join(workspaceRoot, '.env.local'); + + if (fs.existsSync(envPath)) { + dotenv.config({ path: envPath }); + } + if (fs.existsSync(envLocalPath)) { + dotenv.config({ path: envLocalPath, override: true }); + } } function parseBool(value, fallback = false) { @@ -688,7 +709,7 @@ function openDatabase() { }; } - const dbPath = path.join(process.cwd(), 'docstore', 'sqlite3.db'); + const dbPath = path.join(DOCSTORE_DIR, 'sqlite3.db'); fs.mkdirSync(path.dirname(dbPath), { recursive: true }); const sqlite = new BetterSqlite3(dbPath); return { diff --git a/packages/database/src/index.ts b/packages/database/src/index.ts index a6e43ec..f20fd79 100644 --- a/packages/database/src/index.ts +++ b/packages/database/src/index.ts @@ -59,6 +59,21 @@ export function ensureSystemUserExists(userId: string) { } } +function findWorkspaceRoot(startDir: string = process.cwd()): string { + let dir = startDir; + while (true) { + if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) { + return dir; + } + const parent = path.dirname(dir); + if (parent === dir) { + break; + } + dir = parent; + } + return startDir; +} + function getDrizzleDB() { if (dbInstance) return dbInstance; @@ -79,7 +94,8 @@ function getDrizzleDB() { const { drizzle: drizzleSqlite } = require('drizzle-orm/better-sqlite3'); // eslint-disable-next-line @typescript-eslint/no-require-imports const Database = require('better-sqlite3'); - const dbPath = path.join(process.cwd(), 'docstore', 'sqlite3.db'); + const workspaceRoot = findWorkspaceRoot(process.cwd()); + const dbPath = path.join(workspaceRoot, 'docstore', 'sqlite3.db'); const dir = path.dirname(dbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); diff --git a/packages/database/src/migrate.mjs b/packages/database/src/migrate.mjs index 8aba886..b022aad 100644 --- a/packages/database/src/migrate.mjs +++ b/packages/database/src/migrate.mjs @@ -13,8 +13,24 @@ function loadEnvFiles(cwd) { if (fs.existsSync(envLocalPath)) dotenv.config({ path: envLocalPath, override: true }); } +function findWorkspaceRoot(startDir = process.cwd()) { + let dir = startDir; + while (true) { + if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) { + return dir; + } + const parent = path.dirname(dir); + if (parent === dir) { + break; + } + dir = parent; + } + return startDir; +} + export async function runMigrations({ cwd = process.cwd(), env = process.env } = {}) { - loadEnvFiles(cwd); + const workspaceRoot = findWorkspaceRoot(cwd); + loadEnvFiles(workspaceRoot); if (env.POSTGRES_URL?.trim()) { const [{ drizzle }, { migrate }, { Pool }] = await Promise.all([ @@ -33,7 +49,7 @@ export async function runMigrations({ cwd = process.cwd(), env = process.env } = return; } - const dbPath = path.join(cwd, 'docstore', 'sqlite3.db'); + const dbPath = path.join(workspaceRoot, 'docstore', 'sqlite3.db'); fs.mkdirSync(path.dirname(dbPath), { recursive: true }); const [{ drizzle }, { migrate }, { default: BetterSqlite3 }] = await Promise.all([ import('drizzle-orm/better-sqlite3'), @@ -49,3 +65,4 @@ export async function runMigrations({ cwd = process.cwd(), env = process.env } = sqlite.close(); } } +