refactor: replace Biome with oxlint + oxfmt

Migrate the entire monorepo from Biome 2.4.7 to oxlint 1.56.0 (linter)
and oxfmt 0.41.0 (formatter) for faster lint/format and broader rule
coverage.

- Add `.oxlintrc.json` with React, TypeScript, unicorn, import plugins
  and correctness/suspicious categories
- Add `.oxfmtrc.json` with 2-space indent, import sorting, and Tailwind
  class sorting (all 30+ custom className attributes migrated)
- Add `docs/.oxlintrc.json` and `docs/.oxfmtrc.json` with Next.js plugin
- Update all 12 workspace package.json scripts: `oxlint`, `oxfmt`,
  `oxfmt --check`
- Add `format:check` turbo task and CI step
- Update VS Code settings/extensions to use `oxc.oxc-vscode`
- Update CI path triggers from `biome.json` to new config files
- Remove all `biome-ignore` comments and fix shadowed variables
- Delete `biome.json` and `docs/biome.json`
- Reformat entire codebase with oxfmt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 13:26:50 -04:00
co-authored by Claude Opus 4.6
parent a80c8c9a0c
commit 2b0c683b7b
359 changed files with 3894 additions and 7208 deletions
+5 -2
View File
@@ -1,8 +1,11 @@
import { Database } from "bun:sqlite";
import { DATABASE_URL } from "@sofa/config";
import { createLogger } from "@sofa/logger";
import type { Logger } from "drizzle-orm";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { DATABASE_URL } from "@sofa/config";
import { createLogger } from "@sofa/logger";
import * as schema from "./schema";
const log = createLogger("drizzle");
+5 -4
View File
@@ -1,13 +1,14 @@
import path from "node:path";
import { createLogger } from "@sofa/logger";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import { createLogger } from "@sofa/logger";
import { db } from "./client";
const log = createLogger("db");
export function runMigrations(
migrationsFolder = path.join(import.meta.dir, "../drizzle"),
) {
export function runMigrations(migrationsFolder = path.join(import.meta.dir, "../drizzle")) {
log.info("Running database migrations...");
migrate(db, { migrationsFolder });
log.info("Database migrations complete");
+15 -64
View File
@@ -1,11 +1,4 @@
import {
index,
int,
real,
sqliteTable,
text,
uniqueIndex,
} from "drizzle-orm/sqlite-core";
import { index, int, real, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
// Helper for UUID primary keys
const uuidPk = () =>
@@ -19,9 +12,7 @@ export const user = sqliteTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: int("emailVerified", { mode: "boolean" })
.notNull()
.default(false),
emailVerified: int("emailVerified", { mode: "boolean" }).notNull().default(false),
image: text("image"),
role: text("role").default("user"),
banned: int("banned", { mode: "boolean" }).default(false),
@@ -130,11 +121,7 @@ export const titles = sqliteTable(
index("titles_type_releaseDate").on(table.type, table.releaseDate),
index("titles_type_firstAirDate").on(table.type, table.firstAirDate),
index("titles_lastFetchedAt").on(table.lastFetchedAt),
index("titles_type_status_lastFetchedAt").on(
table.type,
table.status,
table.lastFetchedAt,
),
index("titles_type_status_lastFetchedAt").on(table.type, table.status, table.lastFetchedAt),
],
);
@@ -153,12 +140,7 @@ export const seasons = sqliteTable(
airDate: text("airDate"),
lastFetchedAt: int("lastFetchedAt", { mode: "timestamp" }),
},
(table) => [
uniqueIndex("seasons_titleId_seasonNumber").on(
table.titleId,
table.seasonNumber,
),
],
(table) => [uniqueIndex("seasons_titleId_seasonNumber").on(table.titleId, table.seasonNumber)],
);
export const episodes = sqliteTable(
@@ -177,10 +159,7 @@ export const episodes = sqliteTable(
runtimeMinutes: int("runtimeMinutes"),
},
(table) => [
uniqueIndex("episodes_seasonId_episodeNumber").on(
table.seasonId,
table.episodeNumber,
),
uniqueIndex("episodes_seasonId_episodeNumber").on(table.seasonId, table.episodeNumber),
],
);
@@ -200,10 +179,7 @@ export const userTitleStatus = sqliteTable(
updatedAt: int("updatedAt", { mode: "timestamp" }).notNull(),
},
(table) => [
uniqueIndex("userTitleStatus_userId_titleId").on(
table.userId,
table.titleId,
),
uniqueIndex("userTitleStatus_userId_titleId").on(table.userId, table.titleId),
index("userTitleStatus_userId_status").on(table.userId, table.status),
],
);
@@ -226,10 +202,7 @@ export const userMovieWatches = sqliteTable(
.default("manual"),
},
(table) => [
index("userMovieWatches_userId_watchedAt").on(
table.userId,
table.watchedAt,
),
index("userMovieWatches_userId_watchedAt").on(table.userId, table.watchedAt),
index("userMovieWatches_titleId").on(table.titleId),
index("userMovieWatches_userId_titleId").on(table.userId, table.titleId),
],
@@ -253,15 +226,9 @@ export const userEpisodeWatches = sqliteTable(
.default("manual"),
},
(table) => [
index("userEpisodeWatches_userId_watchedAt").on(
table.userId,
table.watchedAt,
),
index("userEpisodeWatches_userId_watchedAt").on(table.userId, table.watchedAt),
index("userEpisodeWatches_episodeId").on(table.episodeId),
index("userEpisodeWatches_userId_episodeId").on(
table.userId,
table.episodeId,
),
index("userEpisodeWatches_userId_episodeId").on(table.userId, table.episodeId),
],
);
@@ -277,9 +244,7 @@ export const userRatings = sqliteTable(
ratingStars: int("ratingStars").notNull(),
ratedAt: int("ratedAt", { mode: "timestamp" }).notNull(),
},
(table) => [
uniqueIndex("userRatings_userId_titleId").on(table.userId, table.titleId),
],
(table) => [uniqueIndex("userRatings_userId_titleId").on(table.userId, table.titleId)],
);
export const availabilityOffers = sqliteTable(
@@ -385,10 +350,7 @@ export const titleCast = sqliteTable(
table.department,
table.character,
),
index("titleCast_titleId_displayOrder").on(
table.titleId,
table.displayOrder,
),
index("titleCast_titleId_displayOrder").on(table.titleId, table.displayOrder),
index("titleCast_personId").on(table.personId),
],
);
@@ -409,10 +371,7 @@ export const personFilmography = sqliteTable(
displayOrder: int("displayOrder").notNull().default(0),
},
(table) => [
index("personFilmography_personId_displayOrder").on(
table.personId,
table.displayOrder,
),
index("personFilmography_personId_displayOrder").on(table.personId, table.displayOrder),
index("personFilmography_titleId").on(table.titleId),
],
);
@@ -434,10 +393,7 @@ export const integrations = sqliteTable(
lastEventAt: int("lastEventAt", { mode: "timestamp" }),
},
(table) => [
uniqueIndex("integrations_userId_provider").on(
table.userId,
table.provider,
),
uniqueIndex("integrations_userId_provider").on(table.userId, table.provider),
uniqueIndex("integrations_token").on(table.token),
],
);
@@ -459,10 +415,7 @@ export const integrationEvents = sqliteTable(
receivedAt: int("receivedAt", { mode: "timestamp" }).notNull(),
},
(table) => [
index("integrationEvents_integrationId_receivedAt").on(
table.integrationId,
table.receivedAt,
),
index("integrationEvents_integrationId_receivedAt").on(table.integrationId, table.receivedAt),
],
);
@@ -481,9 +434,7 @@ export const cronRuns = sqliteTable(
durationMs: int("durationMs"),
errorMessage: text("errorMessage"),
},
(table) => [
index("cronRuns_jobName_startedAt").on(table.jobName, table.startedAt),
],
(table) => [index("cronRuns_jobName_startedAt").on(table.jobName, table.startedAt)],
);
// ─── Import Jobs ────────────────────────────────────────────────────
+13 -41
View File
@@ -1,6 +1,8 @@
import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import * as schema from "./schema";
const {
@@ -28,9 +30,7 @@ export function applyMigrations() {
export function clearAllTables() {
const tables = testClient
.query(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '__drizzle%'",
)
.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE '__drizzle%'")
.all() as { name: string }[];
testClient.run("PRAGMA foreign_keys = OFF");
for (const { name } of tables) {
@@ -79,20 +79,12 @@ export function insertTitle(
return id;
}
export function insertTvShow(
titleId = "tv-1",
tmdbId = 99999,
seasonCount = 1,
epsPerSeason = 3,
) {
export function insertTvShow(titleId = "tv-1", tmdbId = 99999, seasonCount = 1, epsPerSeason = 3) {
insertTitle({ id: titleId, tmdbId, type: "tv", title: "Test Show" });
const episodeIds: string[] = [];
for (let s = 1; s <= seasonCount; s++) {
const seasonId = `${titleId}-s${s}`;
testDb
.insert(seasons)
.values({ id: seasonId, titleId, seasonNumber: s })
.run();
testDb.insert(seasons).values({ id: seasonId, titleId, seasonNumber: s }).run();
for (let e = 1; e <= epsPerSeason; e++) {
const epId = `${titleId}-s${s}e${e}`;
testDb
@@ -110,11 +102,7 @@ export function insertTvShow(
return { titleId, episodeIds };
}
export function insertMovieWatch(
userId: string,
titleId: string,
watchedAt?: Date,
) {
export function insertMovieWatch(userId: string, titleId: string, watchedAt?: Date) {
testDb
.insert(userMovieWatches)
.values({
@@ -126,11 +114,7 @@ export function insertMovieWatch(
.run();
}
export function insertEpisodeWatch(
userId: string,
episodeId: string,
watchedAt?: Date,
) {
export function insertEpisodeWatch(userId: string, episodeId: string, watchedAt?: Date) {
testDb
.insert(userEpisodeWatches)
.values({
@@ -147,22 +131,15 @@ export function insertStatus(
titleId: string,
status: "watchlist" | "in_progress" | "completed",
) {
const now = new Date();
const timestamp = new Date();
testDb
.insert(userTitleStatus)
.values({ userId, titleId, status, addedAt: now, updatedAt: now })
.values({ userId, titleId, status, addedAt: timestamp, updatedAt: timestamp })
.run();
}
export function insertRating(
userId: string,
titleId: string,
ratingStars: number,
) {
testDb
.insert(userRatings)
.values({ userId, titleId, ratingStars, ratedAt: new Date() })
.run();
export function insertRating(userId: string, titleId: string, ratingStars: number) {
testDb.insert(userRatings).values({ userId, titleId, ratingStars, ratedAt: new Date() }).run();
}
export function insertAvailabilityOffer(
@@ -184,13 +161,8 @@ export function insertAvailabilityOffer(
.run();
}
export function insertIntegration(
userId: string,
provider: string,
token = "test-token",
) {
const type =
provider === "sonarr" || provider === "radarr" ? "list" : "webhook";
export function insertIntegration(userId: string, provider: string, token = "test-token") {
const type = provider === "sonarr" || provider === "radarr" ? "list" : "webhook";
return testDb
.insert(integrations)
.values({