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.
This commit is contained in:
Richard R 2026-06-13 14:35:27 -06:00
parent 74a96c0d15
commit ee979205b3
4 changed files with 85 additions and 15 deletions

View file

@ -21,10 +21,26 @@ import {
} from './embedded-seaweedfs.mjs'; } from './embedded-seaweedfs.mjs';
import { resolveEmbeddedWorkerLaunch } from './embedded-worker.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() { function loadEnvFiles() {
const cwd = process.cwd(); const envPath = path.join(workspaceRoot, '.env');
const envPath = path.join(cwd, '.env'); const envLocalPath = path.join(workspaceRoot, '.env.local');
const envLocalPath = path.join(cwd, '.env.local');
if (fs.existsSync(envPath)) { if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath }); dotenv.config({ path: envPath });
@ -111,7 +127,7 @@ function spawnMainCommand(command, env) {
async function runDbMigrations(env) { async function runDbMigrations(env) {
console.log('Running database migrations...'); console.log('Running database migrations...');
await runMigrations({ cwd: process.cwd(), env }); await runMigrations({ cwd: workspaceRoot, env });
} }
function runStorageMigrations(env) { function runStorageMigrations(env) {
@ -278,7 +294,7 @@ async function main() {
} }
if (useEmbeddedWeed) { 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.WEED_MINI_WAIT_SEC = withDefault(runtimeEnv.WEED_MINI_WAIT_SEC, '20');
runtimeEnv.S3_BUCKET = withDefault(runtimeEnv.S3_BUCKET, 'openreader-documents'); runtimeEnv.S3_BUCKET = withDefault(runtimeEnv.S3_BUCKET, 'openreader-documents');
runtimeEnv.S3_REGION = withDefault(runtimeEnv.S3_REGION, 'us-east-1'); 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_WORKER_HOST = withDefault(runtimeEnv.COMPUTE_WORKER_HOST, '127.0.0.1');
runtimeEnv.COMPUTE_NATS_REPLICAS = withDefault(runtimeEnv.COMPUTE_NATS_REPLICAS, '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 }); fs.mkdirSync(natsStoreDir, { recursive: true });
console.log(`Starting embedded nats-server on 127.0.0.1:${embeddedNatsPort}...`); console.log(`Starting embedded nats-server on 127.0.0.1:${embeddedNatsPort}...`);

View file

@ -17,7 +17,23 @@ const { Pool } = require('pg');
const BetterSqlite3 = require('better-sqlite3'); const BetterSqlite3 = require('better-sqlite3');
const ffmpegStatic = require('ffmpeg-static'); 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 DOCUMENTS_V1_DIR = path.join(DOCSTORE_DIR, 'documents_v1');
const AUDIOBOOKS_V1_DIR = path.join(DOCSTORE_DIR, 'audiobooks_v1'); const AUDIOBOOKS_V1_DIR = path.join(DOCSTORE_DIR, 'audiobooks_v1');
const UNCLAIMED_USER_ID = 'unclaimed'; 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; const DOCUMENT_ID_REGEX = /^[a-f0-9]{64}$/i;
function loadEnvFiles() { function loadEnvFiles() {
const envPath = path.join(process.cwd(), '.env'); const envPath = path.join(workspaceRoot, '.env');
const envLocalPath = path.join(process.cwd(), '.env.local'); 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 }); if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
}
if (fs.existsSync(envLocalPath)) {
dotenv.config({ path: envLocalPath, override: true });
}
} }
function parseBool(value, fallback = false) { 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 }); fs.mkdirSync(path.dirname(dbPath), { recursive: true });
const sqlite = new BetterSqlite3(dbPath); const sqlite = new BetterSqlite3(dbPath);
return { return {

View file

@ -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() { function getDrizzleDB() {
if (dbInstance) return dbInstance; if (dbInstance) return dbInstance;
@ -79,7 +94,8 @@ function getDrizzleDB() {
const { drizzle: drizzleSqlite } = require('drizzle-orm/better-sqlite3'); const { drizzle: drizzleSqlite } = require('drizzle-orm/better-sqlite3');
// eslint-disable-next-line @typescript-eslint/no-require-imports // eslint-disable-next-line @typescript-eslint/no-require-imports
const Database = require('better-sqlite3'); 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); const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });

View file

@ -13,8 +13,24 @@ function loadEnvFiles(cwd) {
if (fs.existsSync(envLocalPath)) dotenv.config({ path: envLocalPath, override: true }); 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 } = {}) { export async function runMigrations({ cwd = process.cwd(), env = process.env } = {}) {
loadEnvFiles(cwd); const workspaceRoot = findWorkspaceRoot(cwd);
loadEnvFiles(workspaceRoot);
if (env.POSTGRES_URL?.trim()) { if (env.POSTGRES_URL?.trim()) {
const [{ drizzle }, { migrate }, { Pool }] = await Promise.all([ const [{ drizzle }, { migrate }, { Pool }] = await Promise.all([
@ -33,7 +49,7 @@ export async function runMigrations({ cwd = process.cwd(), env = process.env } =
return; return;
} }
const dbPath = path.join(cwd, 'docstore', 'sqlite3.db'); const dbPath = path.join(workspaceRoot, 'docstore', 'sqlite3.db');
fs.mkdirSync(path.dirname(dbPath), { recursive: true }); fs.mkdirSync(path.dirname(dbPath), { recursive: true });
const [{ drizzle }, { migrate }, { default: BetterSqlite3 }] = await Promise.all([ const [{ drizzle }, { migrate }, { default: BetterSqlite3 }] = await Promise.all([
import('drizzle-orm/better-sqlite3'), import('drizzle-orm/better-sqlite3'),
@ -49,3 +65,4 @@ export async function runMigrations({ cwd = process.cwd(), env = process.env } =
sqlite.close(); sqlite.close();
} }
} }