From c20522946ce20ab6a9cb4742087273d0330f1bb6 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Fri, 12 Jun 2026 15:58:31 +0200 Subject: [PATCH] style: auto-fix lint and format --- .../init/_steps/start/sqlite-restore.tsx | 6 +- .../backup/_components/backup-export-card.tsx | 3 +- .../nextjs/src/app/api/backup/import/route.ts | 11 +- apps/nextjs/src/app/api/backup/shared.ts | 3 +- .../src/app/api/backup/test/backup.spec.ts | 32 ++- .../backup/backup-preview-panel.tsx | 16 +- .../backup/dangerous-action-confirmation.tsx | 11 +- .../backup/database-restore-flow.tsx | 36 ++-- .../backup/restore-progress-panel.tsx | 22 ++- .../components/backup/use-backup-analysis.ts | 187 +++++++++--------- packages/definitions/src/docs/codegen.ts | 13 +- .../integrations/test/technitium-unit.spec.ts | 6 +- 12 files changed, 188 insertions(+), 158 deletions(-) diff --git a/apps/nextjs/src/app/[locale]/init/_steps/start/sqlite-restore.tsx b/apps/nextjs/src/app/[locale]/init/_steps/start/sqlite-restore.tsx index ac5e433df..a1afd8494 100644 --- a/apps/nextjs/src/app/[locale]/init/_steps/start/sqlite-restore.tsx +++ b/apps/nextjs/src/app/[locale]/init/_steps/start/sqlite-restore.tsx @@ -16,11 +16,7 @@ export const InitSqliteRestore = () => { if (expanded) { return ( - diff --git a/apps/nextjs/src/app/[locale]/manage/tools/backup/_components/backup-export-card.tsx b/apps/nextjs/src/app/[locale]/manage/tools/backup/_components/backup-export-card.tsx index cdbe2055e..dc49f71a8 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/backup/_components/backup-export-card.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/backup/_components/backup-export-card.tsx @@ -29,7 +29,8 @@ export const BackupExportCard = () => { const blob = await response.blob(); const url = URL.createObjectURL(blob); - const filename = response.headers.get("Content-Disposition")?.match(/filename="(.+)"/)?.[1] ?? "homarr-backup.zip"; + const filename = + response.headers.get("Content-Disposition")?.match(/filename="(.+)"/)?.[1] ?? "homarr-backup.zip"; const a = document.createElement("a"); a.href = url; diff --git a/apps/nextjs/src/app/api/backup/import/route.ts b/apps/nextjs/src/app/api/backup/import/route.ts index 995cce828..ff789fdea 100644 --- a/apps/nextjs/src/app/api/backup/import/route.ts +++ b/apps/nextjs/src/app/api/backup/import/route.ts @@ -39,7 +39,9 @@ const reEncryptSecrets = (tempDb: InstanceType, importedKeyHex: if (rows.length === 0) return; - const updateStmt = tempDb.prepare('UPDATE "integrationSecret" SET "value" = ? WHERE "integration_id" = ? AND "kind" = ?'); + const updateStmt = tempDb.prepare( + 'UPDATE "integrationSecret" SET "value" = ? WHERE "integration_id" = ? AND "kind" = ?', + ); const transaction = tempDb.transaction(() => { for (const row of rows) { const parts = row.value.split("."); @@ -148,14 +150,13 @@ export async function POST(req: Request) { const rawKey = metadata.encryptionKey ?? zip.getEntry("encryption-key.txt")?.getData().toString().trim(); const importedKey = typeof rawKey === "string" && rawKey.length > 0 ? rawKey : undefined; - const secretCount = ( - tempDb.prepare('SELECT COUNT(*) as count FROM "integrationSecret"').get() as { count: number } - ).count; + const secretCount = (tempDb.prepare('SELECT COUNT(*) as count FROM "integrationSecret"').get() as { count: number }) + .count; if (secretCount > 0 && !importedKey) { throw new Error( "Backup contains integration secrets but no encryption key. " + - "Cannot restore without the original SECRET_ENCRYPTION_KEY.", + "Cannot restore without the original SECRET_ENCRYPTION_KEY.", ); } diff --git a/apps/nextjs/src/app/api/backup/shared.ts b/apps/nextjs/src/app/api/backup/shared.ts index 58978a596..2d93e0bca 100644 --- a/apps/nextjs/src/app/api/backup/shared.ts +++ b/apps/nextjs/src/app/api/backup/shared.ts @@ -8,5 +8,4 @@ const migrationsFolderCandidates = [ "/app/db/migrations/sqlite", ]; -export const findMigrationsFolder = () => - migrationsFolderCandidates.find((candidate) => fs.existsSync(candidate)); +export const findMigrationsFolder = () => migrationsFolderCandidates.find((candidate) => fs.existsSync(candidate)); diff --git a/apps/nextjs/src/app/api/backup/test/backup.spec.ts b/apps/nextjs/src/app/api/backup/test/backup.spec.ts index 4584b415f..0a394abf7 100644 --- a/apps/nextjs/src/app/api/backup/test/backup.spec.ts +++ b/apps/nextjs/src/app/api/backup/test/backup.spec.ts @@ -80,7 +80,10 @@ describe("SQLite backup", () => { zip.writeZip(zipPath); const readBack = new AdmZip(zipPath); - const entryNames = readBack.getEntries().map((e) => e.entryName).sort(); + const entryNames = readBack + .getEntries() + .map((e) => e.entryName) + .toSorted(); expect(entryNames).toEqual(["db.sqlite", "metadata.json"]); const dbEntry = readBack.getEntry("db.sqlite"); @@ -97,9 +100,9 @@ describe("SQLite backup", () => { sqlite.close(); const backupDb = new Database(backupPath, { readonly: true }); - const tables = backupDb - .prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") - .all() as { name: string }[]; + const tables = backupDb.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all() as { + name: string; + }[]; expect(tables.map((t) => t.name)).toContain("board"); expect(tables.map((t) => t.name)).toContain("user"); @@ -124,7 +127,10 @@ describe("SQLite backup", () => { it("should accept valid backup ZIP with all entries", () => { const zip = new AdmZip(); zip.addFile("db.sqlite", Buffer.from("dummy")); - zip.addFile("metadata.json", Buffer.from(JSON.stringify({ dbDialect: "sqlite", encryptionKey: generateHexKey() }))); + zip.addFile( + "metadata.json", + Buffer.from(JSON.stringify({ dbDialect: "sqlite", encryptionKey: generateHexKey() })), + ); const entries = zip.getEntries().map((e) => e.entryName); const required = ["db.sqlite", "metadata.json"]; @@ -173,9 +179,11 @@ describe("SQLite backup", () => { const oldKeyBuf = Buffer.from(oldKey, "hex"); const newKeyBuf = Buffer.from(newKey, "hex"); - const rows = sqlite - .prepare('SELECT "integration_id", "kind", "value" FROM "integrationSecret"') - .all() as { integration_id: string; kind: string; value: string }[]; + const rows = sqlite.prepare('SELECT "integration_id", "kind", "value" FROM "integrationSecret"').all() as { + integration_id: string; + kind: string; + value: string; + }[]; const updateStmt = sqlite.prepare( 'UPDATE "integrationSecret" SET "value" = ? WHERE "integration_id" = ? AND "kind" = ?', @@ -253,7 +261,9 @@ describe("SQLite backup", () => { sqlite.exec(`VACUUM INTO '${backupPath}'`); const backupDb = new Database(backupPath, { readonly: true }); - const app = backupDb.prepare('SELECT "name" FROM "app" WHERE "id" = ?').get("app-1") as { name: string } | undefined; + const app = backupDb.prepare('SELECT "name" FROM "app" WHERE "id" = ?').get("app-1") as + | { name: string } + | undefined; expect(app).toBeDefined(); expect(app?.name).toBe("TestApp"); @@ -281,7 +291,9 @@ describe("SQLite backup", () => { const drizzleDb = drizzle(importDb, { casing: DB_CASING }); migrate(drizzleDb, { migrationsFolder: MIGRATIONS_FOLDER }); - const board = importDb.prepare('SELECT "name" FROM "board" WHERE "id" = ?').get("board-1") as { name: string } | undefined; + const board = importDb.prepare('SELECT "name" FROM "board" WHERE "id" = ?').get("board-1") as + | { name: string } + | undefined; expect(board).toBeDefined(); expect(board?.name).toBe("TestBoard"); diff --git a/apps/nextjs/src/components/backup/backup-preview-panel.tsx b/apps/nextjs/src/components/backup/backup-preview-panel.tsx index 98a5effc5..51dcd8be8 100644 --- a/apps/nextjs/src/components/backup/backup-preview-panel.tsx +++ b/apps/nextjs/src/components/backup/backup-preview-panel.tsx @@ -50,10 +50,10 @@ export const BackupPreviewPanel = ({ analysis }: BackupPreviewPanelProps) => { {t(`entities.${key}` as never)} - {(analysis.counts[key] ?? 0) > 0 && ( - - )} - {analysis.counts[key] ?? 0} + {(analysis.counts[key] ?? 0) > 0 && } + + {analysis.counts[key] ?? 0} + @@ -63,9 +63,13 @@ export const BackupPreviewPanel = ({ analysis }: BackupPreviewPanelProps) => { {analysis.boardNames.length > 0 && ( - {t("boards")}: + + {t("boards")}: + {analysis.boardNames.map((name) => ( - {name} + + {name} + ))} )} diff --git a/apps/nextjs/src/components/backup/dangerous-action-confirmation.tsx b/apps/nextjs/src/components/backup/dangerous-action-confirmation.tsx index 3ebc71ce3..7519f515d 100644 --- a/apps/nextjs/src/components/backup/dangerous-action-confirmation.tsx +++ b/apps/nextjs/src/components/backup/dangerous-action-confirmation.tsx @@ -49,7 +49,9 @@ export const DangerousActionConfirmation = ({ }> - {warningTitle} + + {warningTitle} + {warningBody} @@ -76,12 +78,7 @@ export const DangerousActionConfirmation = ({ - diff --git a/apps/nextjs/src/components/backup/database-restore-flow.tsx b/apps/nextjs/src/components/backup/database-restore-flow.tsx index 502b5d9f1..83d8f9f3f 100644 --- a/apps/nextjs/src/components/backup/database-restore-flow.tsx +++ b/apps/nextjs/src/components/backup/database-restore-flow.tsx @@ -115,13 +115,19 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat > - + - +
@@ -143,8 +149,12 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat
- {file?.name} - {file ? `${(file.size / 1024 / 1024).toFixed(2)} MB` : ""} + + {file?.name} + + + {file ? `${(file.size / 1024 / 1024).toFixed(2)} MB` : ""} +
- {loading && migrationProgress && ( - - )} + {loading && migrationProgress && } {loading && !migrationProgress && ( - {t("analyzing")} + + {t("analyzing")} + )} @@ -173,10 +183,7 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat <> - @@ -190,10 +197,7 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat return ( {analysis && } - setStep("preview")} - /> + setStep("preview")} /> ); } diff --git a/apps/nextjs/src/components/backup/restore-progress-panel.tsx b/apps/nextjs/src/components/backup/restore-progress-panel.tsx index ebbbf9330..63af14a75 100644 --- a/apps/nextjs/src/components/backup/restore-progress-panel.tsx +++ b/apps/nextjs/src/components/backup/restore-progress-panel.tsx @@ -91,7 +91,9 @@ export const RestoreProgressPanel = ({ active, migrations, onComplete }: Restore }; void run(); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; }, [active, onComplete, steps]); if (!active && currentStepIdx === -1) return null; @@ -107,12 +109,7 @@ export const RestoreProgressPanel = ({ active, migrations, onComplete }: Restore - + {steps.map((step, i) => { const isDone = i < currentStepIdx; const isActive = i === currentStepIdx; @@ -138,7 +135,16 @@ export const RestoreProgressPanel = ({ active, migrations, onComplete }: Restore } > - + { const [migrationProgress, setMigrationProgress] = useState(null); const dbRef = useRef(null); - const analyzeFile = useCallback(async (file: File) => { - setLoading(true); - setError(null); - setAnalysis(null); - setMigrationProgress(null); - - try { - const [JSZip, initSqlJs, allMigrations, wasmBinary] = await Promise.all([ - import("jszip").then((m) => m.default), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - import("sql.js").then((m: any) => m.default), - fetchMigrations(), - fetch("/api/backup/sql-wasm").then((r) => { - if (!r.ok) throw new Error("Failed to load SQL WASM binary"); - return r.arrayBuffer(); - }), - ]); - - const arrayBuffer = await file.arrayBuffer(); - const zip = await JSZip.loadAsync(arrayBuffer); - - const metadataFile = zip.file("metadata.json"); - if (!metadataFile) throw new Error(t("invalidMissingMetadata")); - const dbFile = zip.file("db.sqlite"); - if (!dbFile) throw new Error(t("invalidMissingDb")); - - const metadata = JSON.parse(await metadataFile.async("string")); - const dbBuffer = await dbFile.async("uint8array"); - - const SQL = await initSqlJs({ wasmBinary }); - - const db = new SQL.Database(dbBuffer); - dbRef.current = db; + const analyzeFile = useCallback( + async (file: File) => { + setLoading(true); + setError(null); + setAnalysis(null); + setMigrationProgress(null); try { - const appliedCount = getAppliedMigrationCount(db); - const pendingMigrations = allMigrations.filter((m) => m.idx >= appliedCount); + const [JSZip, initSqlJs, allMigrations, wasmBinary] = await Promise.all([ + import("jszip").then((m) => m.default), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + import("sql.js").then((m: any) => m.default), + fetchMigrations(), + fetch("/api/backup/sql-wasm").then((r) => { + if (!r.ok) throw new Error("Failed to load SQL WASM binary"); + return r.arrayBuffer(); + }), + ]); - const migrations: MigrationStatus = { - applied: appliedCount, - pending: pendingMigrations, - total: allMigrations.length, - }; + const arrayBuffer = await file.arrayBuffer(); + const zip = await JSZip.loadAsync(arrayBuffer); - if (pendingMigrations.length > 0) { - for (const [i, migration] of pendingMigrations.entries()) { - setMigrationProgress({ - current: i + 1, - total: pendingMigrations.length, - tag: migration.tag, - phase: "applying", - }); + const metadataFile = zip.file("metadata.json"); + if (!metadataFile) throw new Error(t("invalidMissingMetadata")); + const dbFile = zip.file("db.sqlite"); + if (!dbFile) throw new Error(t("invalidMissingDb")); - await delay(100); + const metadata = JSON.parse(await metadataFile.async("string")); + const dbBuffer = await dbFile.async("uint8array"); - try { - const statements = migration.sql - .split("--> statement-breakpoint") - .map((s) => s.trim()) - .filter(Boolean); + const SQL = await initSqlJs({ wasmBinary }); - for (const stmt of statements) { - db.run(stmt); - } + const db = new SQL.Database(dbBuffer); + dbRef.current = db; - db.run( - `INSERT INTO "${DRIZZLE_MIGRATIONS_TABLE}" ("hash", "created_at") VALUES (?, ?)`, - [migration.tag, Date.now()], - ); - } catch (migrationErr) { + try { + const appliedCount = getAppliedMigrationCount(db); + const pendingMigrations = allMigrations.filter((m) => m.idx >= appliedCount); + + const migrations: MigrationStatus = { + applied: appliedCount, + pending: pendingMigrations, + total: allMigrations.length, + }; + + if (pendingMigrations.length > 0) { + for (const [i, migration] of pendingMigrations.entries()) { setMigrationProgress({ current: i + 1, total: pendingMigrations.length, tag: migration.tag, - phase: "error", + phase: "applying", }); - throw new Error( - `Migration ${migration.tag} failed: ${migrationErr instanceof Error ? migrationErr.message : "Unknown error"}`, - ); + + await delay(100); + + try { + const statements = migration.sql + .split("--> statement-breakpoint") + .map((s) => s.trim()) + .filter(Boolean); + + for (const stmt of statements) { + db.run(stmt); + } + + db.run(`INSERT INTO "${DRIZZLE_MIGRATIONS_TABLE}" ("hash", "created_at") VALUES (?, ?)`, [ + migration.tag, + Date.now(), + ]); + } catch (migrationErr) { + setMigrationProgress({ + current: i + 1, + total: pendingMigrations.length, + tag: migration.tag, + phase: "error", + }); + throw new Error( + `Migration ${migration.tag} failed: ${migrationErr instanceof Error ? migrationErr.message : "Unknown error"}`, { cause: migrationErr }, + ); + } + + setMigrationProgress({ + current: i + 1, + total: pendingMigrations.length, + tag: migration.tag, + phase: "done", + }); + + await delay(50); } - - setMigrationProgress({ - current: i + 1, - total: pendingMigrations.length, - tag: migration.tag, - phase: "done", - }); - - await delay(50); } + + const counts = countEntities(db); + const boardNames = getBoardNames(db); + + setMigrationProgress(null); + setAnalysis({ metadata, counts, boardNames, migrations }); + } finally { + db.close(); + dbRef.current = null; } - - const counts = countEntities(db); - const boardNames = getBoardNames(db); - - setMigrationProgress(null); - setAnalysis({ metadata, counts, boardNames, migrations }); + } catch (err) { + setError(err instanceof Error ? err.message : t("analyzeError")); } finally { - db.close(); - dbRef.current = null; + setLoading(false); } - } catch (err) { - setError(err instanceof Error ? err.message : t("analyzeError")); - } finally { - setLoading(false); - } - }, [t]); + }, + [t], + ); const reset = useCallback(() => { if (dbRef.current) { - try { dbRef.current.close(); } catch { /* already closed */ } + try { + dbRef.current.close(); + } catch { + /* already closed */ + } dbRef.current = null; } setAnalysis(null); diff --git a/packages/definitions/src/docs/codegen.ts b/packages/definitions/src/docs/codegen.ts index a4355bd26..d9185e59c 100644 --- a/packages/definitions/src/docs/codegen.ts +++ b/packages/definitions/src/docs/codegen.ts @@ -60,8 +60,12 @@ const updateSitemapTypeFileAsync = async (sitemapPathType: string) => { }; const slugMapPaths = [ - ...Object.values(integrationDocSlugs).filter(Boolean).map((slug) => `/docs/integrations/${slug}`), - ...Object.values(widgetDocSlugs).filter(Boolean).map((slug) => `/docs/widgets/${slug}`), + ...Object.values(integrationDocSlugs) + .filter(Boolean) + .map((slug) => `/docs/integrations/${slug}`), + ...Object.values(widgetDocSlugs) + .filter(Boolean) + .map((slug) => `/docs/widgets/${slug}`), ]; const outputPath = path.join(__dirname, "homarr-docs-sitemap.ts"); @@ -73,7 +77,10 @@ const main = async () => { const sitemapData = parseXml(sitemapXml); paths = mapSitemapXmlToPaths(sitemapData); } catch { - const exists = await fs.access(outputPath).then(() => true, () => false); + const exists = await fs.access(outputPath).then( + () => true, + () => false, + ); if (exists) { console.warn("Could not fetch sitemap from homarr.dev, keeping existing generated file"); return; diff --git a/packages/integrations/test/technitium-unit.spec.ts b/packages/integrations/test/technitium-unit.spec.ts index e9e4ef975..f8707cfc5 100644 --- a/packages/integrations/test/technitium-unit.spec.ts +++ b/packages/integrations/test/technitium-unit.spec.ts @@ -748,10 +748,7 @@ describe("auth mechanism per version", () => { // ─── testingAsync (session cleanup) ────────────────────────────────────────── // Calls the protected testingAsync directly with a custom fetchAsync. -function callTestingAsync( - integration: TechnitiumDnsIntegration, - fetchAsync: typeof fetchWithTrustedCertificatesAsync, -) { +function callTestingAsync(integration: TechnitiumDnsIntegration, fetchAsync: typeof fetchWithTrustedCertificatesAsync) { return ( integration as unknown as { testingAsync: (input: { fetchAsync: typeof fetchWithTrustedCertificatesAsync }) => Promise; @@ -760,7 +757,6 @@ function callTestingAsync( } describe("testingAsync (session cleanup)", () => { - test("credentials auth calls logout endpoint after successful test", async () => { sessionData = null; const fetchMock = vi.fn();