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
+18 -35
View File
@@ -1,5 +1,7 @@
import { describe, expect, test } from "bun:test";
import AdmZip from "adm-zip";
import {
type ParseResult,
parseLetterboxdExport,
@@ -747,9 +749,7 @@ describe("parseLetterboxdExport", () => {
expect(result.data.movies).toHaveLength(1);
// Should have warnings for missing watched.csv, watchlist.csv, ratings.csv
const missingWarnings = result.warnings.filter((w) =>
w.includes("not found in export"),
);
const missingWarnings = result.warnings.filter((w) => w.includes("not found in export"));
expect(missingWarnings).toHaveLength(3);
expect(missingWarnings.some((w) => w.includes("watched.csv"))).toBe(true);
expect(missingWarnings.some((w) => w.includes("watchlist.csv"))).toBe(true);
@@ -779,8 +779,7 @@ describe("parseLetterboxdExport", () => {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date\n2024-01-15,Inception,2010,https://boxd.it/123,,false,,2024-01-14",
"watched.csv":
"Date,Name,Year,Letterboxd URI\n2024-01-10,Inception,2010,https://boxd.it/123",
"watched.csv": "Date,Name,Year,Letterboxd URI\n2024-01-10,Inception,2010,https://boxd.it/123",
"watchlist.csv": "Date,Name,Year,Letterboxd URI",
"ratings.csv": "Date,Name,Year,Letterboxd URI,Rating",
});
@@ -795,8 +794,7 @@ describe("parseLetterboxdExport", () => {
describe("rating conversion (Letterboxd 0.5-5 to 1-5)", () => {
async function rateLetterboxd(rating: number) {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"diary.csv": "Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"watched.csv": "Date,Name,Year,Letterboxd URI",
"watchlist.csv": "Date,Name,Year,Letterboxd URI",
"ratings.csv": `Date,Name,Year,Letterboxd URI,Rating\n2024-01-15,Test,2020,https://boxd.it/1,${rating}`,
@@ -857,21 +855,17 @@ describe("parseLetterboxdExport", () => {
test("skips rating 0 with warning", async () => {
const result = await rateLetterboxd(0);
expect(result.data.ratings).toHaveLength(0);
expect(
result.warnings.some((w) =>
w.includes("Skipped invalid Letterboxd rating"),
),
).toBe(true);
expect(result.warnings.some((w) => w.includes("Skipped invalid Letterboxd rating"))).toBe(
true,
);
});
test("skips rating 6 with warning", async () => {
const result = await rateLetterboxd(6);
expect(result.data.ratings).toHaveLength(0);
expect(
result.warnings.some((w) =>
w.includes("Skipped invalid Letterboxd rating"),
),
).toBe(true);
expect(result.warnings.some((w) => w.includes("Skipped invalid Letterboxd rating"))).toBe(
true,
);
});
});
@@ -940,9 +934,7 @@ describe("parseLetterboxdExport", () => {
const result = await parseLetterboxdExport(invalidBlob);
expect(result.data.movies).toHaveLength(0);
expect(result.warnings.some((w) => w.includes("Failed to read ZIP"))).toBe(
true,
);
expect(result.warnings.some((w) => w.includes("Failed to read ZIP"))).toBe(true);
});
test("handles CSV files nested in a subdirectory", async () => {
@@ -980,8 +972,7 @@ describe("parseLetterboxdExport", () => {
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date\n2024-01-15,Movie A,2020,https://boxd.it/1,,false,,2024-01-14\n2024-01-16,Movie B,2021,https://boxd.it/2,,false,,2024-01-15",
"watched.csv": "Date,Name,Year,Letterboxd URI",
"watchlist.csv":
"Date,Name,Year,Letterboxd URI\n2024-01-20,Movie C,2022,https://boxd.it/3",
"watchlist.csv": "Date,Name,Year,Letterboxd URI\n2024-01-20,Movie C,2022,https://boxd.it/3",
"ratings.csv":
"Date,Name,Year,Letterboxd URI,Rating\n2024-01-15,Movie A,2020,https://boxd.it/1,4",
});
@@ -994,8 +985,7 @@ describe("parseLetterboxdExport", () => {
test("ratings.csv with missing rating column skips the row", async () => {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"diary.csv": "Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"watched.csv": "Date,Name,Year,Letterboxd URI",
"watchlist.csv": "Date,Name,Year,Letterboxd URI",
"ratings.csv":
@@ -1009,8 +999,7 @@ describe("parseLetterboxdExport", () => {
test("multiple movies in watched.csv are all parsed", async () => {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"diary.csv": "Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"watched.csv":
"Date,Name,Year,Letterboxd URI\n2024-01-10,Movie A,2020,https://boxd.it/1\n2024-01-11,Movie B,2021,https://boxd.it/2\n2024-01-12,Movie C,2022,https://boxd.it/3",
"watchlist.csv": "Date,Name,Year,Letterboxd URI",
@@ -1020,17 +1009,12 @@ describe("parseLetterboxdExport", () => {
const result = await parseLetterboxdExport(zip);
expect(result.data.movies).toHaveLength(3);
expect(result.data.movies.map((m) => m.title)).toEqual([
"Movie A",
"Movie B",
"Movie C",
]);
expect(result.data.movies.map((m) => m.title)).toEqual(["Movie A", "Movie B", "Movie C"]);
});
test("uses Title column as fallback when Name column is absent", async () => {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"diary.csv": "Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"watched.csv":
"Date,Title,Year,Letterboxd URI\n2024-01-10,Fallback Title,2020,https://boxd.it/1",
"watchlist.csv": "Date,Name,Year,Letterboxd URI",
@@ -1045,8 +1029,7 @@ describe("parseLetterboxdExport", () => {
test("watchlist items all have type movie", async () => {
const zip = createLetterboxdZip({
"diary.csv":
"Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"diary.csv": "Date,Name,Year,Letterboxd URI,Rating,Rewatch,Tags,Watched Date",
"watched.csv": "Date,Name,Year,Letterboxd URI",
"watchlist.csv":
"Date,Name,Year,Letterboxd URI\n2024-01-20,WL Item A,2024,https://boxd.it/1\n2024-01-21,WL Item B,2025,https://boxd.it/2",