From ee979205b3f3e3eaea6d8dcdca3eecff981e6510 Mon Sep 17 00:00:00 2001 From: Richard R Date: Sat, 13 Jun 2026 14:35:27 -0600 Subject: [PATCH] fix(storage): unify workspace root resolution for docstore and migrations Adopt a consistent findWorkspaceRoot helper across bootstrap, storage, and database modules to reliably locate the monorepo root. Update environment file loading and docstore path construction to use the workspace root, ensuring correct file resolution in multi-package setups. This addresses issues with relative paths and improves compatibility in monorepo and containerized environments. --- packages/bootstrap/src/cli.mjs | 28 +++++++++++++---- packages/bootstrap/src/storage-migration.mjs | 33 ++++++++++++++++---- packages/database/src/index.ts | 18 ++++++++++- packages/database/src/migrate.mjs | 21 +++++++++++-- 4 files changed, 85 insertions(+), 15 deletions(-) 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(); } } +