mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-08 00:02:20 +03:00
Turbopack (Next.js 15) does not process node: URL prefixes correctly when
bundling server-side files that get transitively included. Removed the node:
prefix from 17 files:
- src/lib/db/migrationRunner.ts (node:fs, node:path, node:url)
- src/lib/db/core.ts (node:path, node:fs)
- src/lib/db/backup.ts (node:path, node:fs)
- src/lib/db/prompts.ts (node:fs)
- src/lib/dataPaths.ts (node:path, node:os)
- src/app/api/settings/route.ts
- src/app/api/storage/health/route.ts
- src/app/api/oauth/[provider]/[action]/route.ts
- src/app/api/db-backups/{exportAll,import,export}/route.ts
- src/shared/middleware/correlationId.ts
- src/shared/utils/requestId.ts
- src/lib/apiBridgeServer.ts
- src/lib/cacheLayer.ts
- src/lib/semanticCache.ts
- src/lib/oauth/providers/kimi-coding.ts
Also updated generate-release.md: Docker Hub sync and dual-VPS deploy
are now mandatory steps in every release.
144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
/**
|
|
* Migration Runner — Versioned SQL Migrations for SQLite
|
|
*
|
|
* Reads numbered `.sql` files from the migrations directory and applies
|
|
* them sequentially, tracking applied versions in a `schema_migrations` table.
|
|
*
|
|
* Naming convention: `NNN_description.sql` (e.g., `001_initial_schema.sql`)
|
|
*
|
|
* All migrations run within a single transaction — all-or-nothing per file.
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import type Database from "better-sqlite3";
|
|
|
|
/**
|
|
* Resolve the migrations directory path safely across platforms.
|
|
* On Windows with global npm installs, `import.meta.url` may not be a valid
|
|
* `file://` URL, causing `fileURLToPath` to throw `ERR_INVALID_FILE_URL_PATH`.
|
|
*/
|
|
function resolveMigrationsDir(): string {
|
|
try {
|
|
const metaUrl = import.meta.url;
|
|
if (metaUrl && metaUrl.startsWith("file://")) {
|
|
const __filename = fileURLToPath(metaUrl);
|
|
return path.join(path.dirname(__filename), "migrations");
|
|
}
|
|
} catch {
|
|
// fileURLToPath failed (e.g. Windows global install) — use fallback
|
|
}
|
|
// Fallback: resolve relative to cwd (works for both dev and global installs)
|
|
return path.join(process.cwd(), "src", "lib", "db", "migrations");
|
|
}
|
|
|
|
const MIGRATIONS_DIR = resolveMigrationsDir();
|
|
|
|
/**
|
|
* Ensure the schema_migrations tracking table exists.
|
|
*/
|
|
function ensureMigrationsTable(db: Database.Database): void {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
|
|
version TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
`);
|
|
}
|
|
|
|
/**
|
|
* Get all migration files sorted by version number.
|
|
*/
|
|
function getMigrationFiles(): Array<{ version: string; name: string; path: string }> {
|
|
if (!fs.existsSync(MIGRATIONS_DIR)) return [];
|
|
|
|
return fs
|
|
.readdirSync(MIGRATIONS_DIR)
|
|
.filter((f) => f.endsWith(".sql"))
|
|
.sort()
|
|
.map((filename) => {
|
|
const match = filename.match(/^(\d+)_(.+)\.sql$/);
|
|
if (!match) return null;
|
|
return {
|
|
version: match[1],
|
|
name: match[2],
|
|
path: path.join(MIGRATIONS_DIR, filename),
|
|
};
|
|
})
|
|
.filter(Boolean) as Array<{ version: string; name: string; path: string }>;
|
|
}
|
|
|
|
/**
|
|
* Get list of already-applied migration versions.
|
|
*/
|
|
function getAppliedVersions(db: Database.Database): Set<string> {
|
|
const rows = db.prepare("SELECT version FROM _omniroute_migrations").all() as Array<{
|
|
version: string;
|
|
}>;
|
|
return new Set(rows.map((r) => r.version));
|
|
}
|
|
|
|
/**
|
|
* Run all pending migrations in order.
|
|
* Returns the number of migrations applied.
|
|
*/
|
|
export function runMigrations(db: Database.Database): number {
|
|
ensureMigrationsTable(db);
|
|
|
|
const files = getMigrationFiles();
|
|
const applied = getAppliedVersions(db);
|
|
let count = 0;
|
|
|
|
for (const migration of files) {
|
|
if (applied.has(migration.version)) continue;
|
|
|
|
const sql = fs.readFileSync(migration.path, "utf-8");
|
|
|
|
const applyMigration = db.transaction(() => {
|
|
db.exec(sql);
|
|
db.prepare("INSERT INTO _omniroute_migrations (version, name) VALUES (?, ?)").run(
|
|
migration.version,
|
|
migration.name
|
|
);
|
|
});
|
|
|
|
try {
|
|
applyMigration();
|
|
count++;
|
|
console.log(`[Migration] Applied: ${migration.version}_${migration.name}`);
|
|
} catch (err: unknown) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.error(`[Migration] FAILED: ${migration.version}_${migration.name} — ${message}`);
|
|
throw err; // Re-throw to prevent DB from starting in inconsistent state
|
|
}
|
|
}
|
|
|
|
if (count > 0) {
|
|
console.log(`[Migration] ${count} migration(s) applied successfully.`);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Get migration status for diagnostics.
|
|
*/
|
|
export function getMigrationStatus(db: Database.Database): {
|
|
applied: Array<{ version: string; name: string; applied_at: string }>;
|
|
pending: Array<{ version: string; name: string }>;
|
|
} {
|
|
ensureMigrationsTable(db);
|
|
|
|
const appliedRows = db
|
|
.prepare("SELECT version, name, applied_at FROM _omniroute_migrations ORDER BY version")
|
|
.all() as Array<{ version: string; name: string; applied_at: string }>;
|
|
|
|
const appliedVersions = new Set(appliedRows.map((r) => r.version));
|
|
const allFiles = getMigrationFiles();
|
|
const pending = allFiles.filter((f) => !appliedVersions.has(f.version));
|
|
|
|
return { applied: appliedRows, pending };
|
|
}
|