chore: add pre-commit hooks, tighten ESLint, activate Sentry DSN, publish CI coverage (Phase 1)

- Install husky v9 + lint-staged: pre-commit runs eslint --fix and prettier on staged files
- Tighten ESLint base config: no-console→error, ban-ts-comment (ts-ignore banned, ts-expect-error with description allowed), reportUnusedDisableDirectives→error
- Migrate web app from deprecated `next lint` to `eslint src/` with flat config and react-hooks plugin
- Convert all 5 @ts-ignore to @ts-expect-error with descriptions, remove stale disable comments
- Add NEXT_PUBLIC_SENTRY_DSN to docker-compose.prod.yml and .env.example
- Add coverage artifact upload step to CI test job
- Pre-existing violations (102 warnings) downgraded to warn in web config for Phase 2 cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 14:49:29 +02:00
parent 605fd7cea1
commit 82acc56b8d
38 changed files with 2901 additions and 1251 deletions
@@ -115,7 +115,6 @@ export async function GET(request: Request) {
)
.join("; ");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await createNotificationsForUsers({
db: prisma as any,
userIds: adminUsers.map((u) => u.id),
@@ -128,7 +127,10 @@ export async function GET(request: Request) {
});
logger.warn(
{ anomalies: report.anomalies, window: { start: report.windowStartedAt, end: report.windowEndedAt } },
{
anomalies: report.anomalies,
window: { start: report.windowStartedAt, end: report.windowEndedAt },
},
"Auth anomaly cron: anomalies detected and admins notified",
);
}
@@ -140,9 +142,6 @@ export async function GET(request: Request) {
});
} catch (error) {
logger.error({ error, route: "/api/cron/auth-anomaly-check" }, "Auth anomaly cron failed");
return NextResponse.json(
{ ok: false, error: "Internal error" },
{ status: 500 },
);
return NextResponse.json({ ok: false, error: "Internal error" }, { status: 500 });
}
}
@@ -73,10 +73,7 @@ export async function GET(request: Request) {
if (deny) return deny;
try {
const [postgres, redis] = await Promise.all([
checkPostgres(),
checkRedis(),
]);
const [postgres, redis] = await Promise.all([checkPostgres(), checkRedis()]);
const allHealthy = postgres.status === "ok" && redis.status === "ok";
@@ -92,7 +89,6 @@ export async function GET(request: Request) {
});
if (adminUsers.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await createNotificationsForUsers({
db: prisma as any,
userIds: adminUsers.map((u) => u.id),
@@ -121,9 +117,6 @@ export async function GET(request: Request) {
);
} catch (error) {
logger.error({ error, route: "/api/cron/health-check" }, "Health check cron failed");
return NextResponse.json(
{ ok: false, error: "Internal error" },
{ status: 500 },
);
return NextResponse.json({ ok: false, error: "Internal error" }, { status: 500 });
}
}
@@ -17,7 +17,7 @@ const MINIMUM_SAFE_VERSIONS: Record<string, { minVersion: string; advisory?: str
next: { minVersion: "15.0.0", advisory: "Keep Next.js on latest 15.x" },
prisma: { minVersion: "6.0.0", advisory: "Prisma 6.x for latest security patches" },
"@sentry/nextjs": { minVersion: "8.0.0", advisory: "Sentry v8 for latest fixes" },
"ioredis": { minVersion: "5.4.0", advisory: "ioredis 5.4+ for connection security" },
ioredis: { minVersion: "5.4.0", advisory: "ioredis 5.4+ for connection security" },
"next-auth": { minVersion: "5.0.0", advisory: "Auth.js v5 for security hardening" },
};
@@ -89,7 +89,10 @@ function scanPackageJson(): Finding[] {
}
}
} catch (error) {
logger.error({ error, route: "/api/cron/security-audit" }, "Failed to scan package manifests for security audit");
logger.error(
{ error, route: "/api/cron/security-audit" },
"Failed to scan package manifests for security audit",
);
}
return findings;
@@ -124,7 +127,6 @@ export async function GET(request: Request) {
.map((f) => `${f.package}@${f.currentVersion} (need >=${f.minimumVersion})`)
.join(", ");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await createNotificationsForUsers({
db: prisma as any,
userIds: adminUsers.map((u) => u.id),
@@ -147,9 +149,6 @@ export async function GET(request: Request) {
});
} catch (error) {
logger.error({ error, route: "/api/cron/security-audit" }, "Security audit cron failed");
return NextResponse.json(
{ ok: false, error: "Internal error" },
{ status: 500 },
);
return NextResponse.json({ ok: false, error: "Internal error" }, { status: 500 });
}
}