From f75bbaeb37b203c590f1d00bf51beef97723e835 Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:56:48 -0400 Subject: [PATCH] Enforce type-aware TypeScript linting and remove unsafe assertions (#15) * Enforce safe TypeScript assertions and validate runtime payloads * Fix CI knip config and floating promise lint * Validate DataForSEO payloads with Zod schemas Replace weak object guards with endpoint-level schema parsing so invalid API shapes fail fast instead of being silently filtered. Align downstream keyword mapping with the stricter validated payload contracts. --- .oxlintrc.json | 5 + README.md | 2 + knip.jsonc | 3 +- package.json | 7 +- pnpm-lock.yaml | 69 ++- .../components/DefaultCatchBoundary.tsx | 4 +- src/routes/p/$projectId/audit/index.tsx | 29 +- .../p/$projectId/audit/issues/$resultId.tsx | 2 +- src/routes/p/$projectId/domain.tsx | 15 +- src/routes/p/$projectId/keywords.tsx | 23 +- .../p/$projectId/psi/issues/$resultId.tsx | 2 +- src/server/lib/audit/discovery.ts | 15 +- src/server/lib/audit/page-analyzer.ts | 5 +- src/server/lib/audit/psi.ts | 2 +- src/server/lib/audit/url-policy.ts | 2 +- src/server/lib/dataforseo.ts | 395 +++++++++++------- src/server/lib/kv-cache.ts | 4 +- src/server/services/AuditService.ts | 33 +- src/server/services/DomainService.ts | 41 +- src/server/services/KeywordResearchService.ts | 88 +++- src/server/services/PsiIssuesService.ts | 72 +++- src/server/services/PsiService.ts | 90 ++-- .../keyword-research/research-data.ts | 21 +- .../keyword-research/saved-keywords.ts | 11 +- src/server/services/keyword-research/serp.ts | 26 +- tsconfig.json | 1 - 26 files changed, 674 insertions(+), 293 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 0225b30..09b1df5 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -12,6 +12,11 @@ "unicorn/no-array-sort": "error", "typescript/no-explicit-any": "error", "typescript/consistent-type-imports": "error", + "typescript/no-unsafe-type-assertion": "error", + "typescript/no-unnecessary-type-assertion": [ + "error", + { "checkLiteralConstAssertions": false } + ], "eslint/no-constant-binary-expression": "error", "eslint/no-self-assign": "error", "eslint/no-unreachable-loop": "error", diff --git a/README.md b/README.md index c4c6d89..8b5a6af 100644 --- a/README.md +++ b/README.md @@ -146,11 +146,13 @@ pnpm run db:migrate:local ``` Configure .env.local: + 1. `cp .env.example .env.local` 2. Add `AUTH_MODE=local_noauth` so that it doesn't expect Cloudflare Access 3. Add `DATAFORSEO_API_KEY=yourkey` Run Locally: + ``` # Option 1 pnpm run dev diff --git a/knip.jsonc b/knip.jsonc index 5916d00..a8ec800 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,5 +1,5 @@ { - "ignoreBinaries": [], + "ignoreBinaries": ["portless"], "entry": [ // Detect Tanstack Start Routes "src/router.tsx", @@ -40,5 +40,6 @@ "daisyui", "@tanstack/query-sync-storage-persister", "@tanstack/react-query-persist-client", + "portless", ], } diff --git a/package.json b/package.json index fddb8be..b29b628 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "dev": "AUTH_MODE=local_noauth vite dev", "dev:agents": "mkdir -p .logs && AUTH_MODE=local_noauth portless run vite dev 2>&1 | tee .logs/dev-server.log", "build": "vite build && tsc --noEmit", - "lint": "oxlint .", - "lint:fix": "oxlint . --fix", + "lint": "oxlint . --type-aware", + "lint:fix": "oxlint . --type-aware --fix", "preview": "npm run build && vite preview --port 3001", "deploy": "npm run db:migrate:prod && npm run build && wrangler deploy", "cf-typegen": "wrangler types", @@ -19,7 +19,7 @@ "db:migrate:local": "wrangler d1 migrations apply DB --local", "db:migrate:prod": "wrangler d1 migrations apply DB --remote", "knip": "knip", - "ci": "prettier --check . && knip && tsc --noEmit && oxlint ." + "ci": "prettier --check . && knip && tsc --noEmit && oxlint . --type-aware" }, "cloudflare": { "bindings": { @@ -78,6 +78,7 @@ "drizzle-kit": "^0.31.4", "knip": "^5.66.4", "oxlint": "^1.50.0", + "oxlint-tsgolint": "^0.15.0", "portless": "^0.5.2", "prettier": "^3.6.2", "typescript": "^5.9.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed837bb..6ab2058 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,7 +122,10 @@ importers: version: 5.85.0(@types/node@22.19.11)(typescript@5.9.3) oxlint: specifier: ^1.50.0 - version: 1.50.0 + version: 1.50.0(oxlint-tsgolint@0.15.0) + oxlint-tsgolint: + specifier: ^0.15.0 + version: 0.15.0 portless: specifier: ^0.5.2 version: 0.5.2 @@ -1146,6 +1149,36 @@ packages: cpu: [x64] os: [win32] + '@oxlint-tsgolint/darwin-arm64@0.15.0': + resolution: {integrity: sha512-d7Ch+A6hic+RYrm32+Gh1o4lOrQqnFsHi721ORdHUDBiQPea+dssKUEMwIbA6MKmCy6TVJ02sQyi24OEfCiGzw==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.15.0': + resolution: {integrity: sha512-Aoai2wAkaUJqp/uEs1gml6TbaPW4YmyO5Ai/vOSkiizgHqVctjhjKqmRiWTX2xuPY94VkwOLqp+Qr3y/0qSpWQ==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.15.0': + resolution: {integrity: sha512-4og13a7ec4Vku5t2Y7s3zx6YJP6IKadb1uA9fOoRH6lm/wHWoCnxjcfJmKHXRZJII81WmbdJMSPxaBfwN/S68Q==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.15.0': + resolution: {integrity: sha512-9b9xzh/1Harn3a+XiKTK/8LrWw3VcqLfYp/vhV5/zAVR2Mt0d63WSp4FL+wG7DKnI2T/CbMFUFHwc7kCQjDMzQ==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.15.0': + resolution: {integrity: sha512-nNac5hewHdkk5mowOwTqB1ZD76zB/FsUiyUvdCyupq5cG54XyKqSLEp9QGbx7wFJkWCkeWmuwRed4sfpAlKaeA==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.15.0': + resolution: {integrity: sha512-ioAY2XLpy83E2EqOLH9p1cEgj0G2qB1lmAn0a3yFV1jHQB29LIPIKGNsu/tYCClpwmHN79pT5KZAHZOgWxxqNg==} + cpu: [x64] + os: [win32] + '@oxlint/binding-android-arm-eabi@1.50.0': resolution: {integrity: sha512-G7MRGk/6NCe+L8ntonRdZP7IkBfEpiZ/he3buLK6JkLgMHgJShXZ+BeOwADmspXez7U7F7L1Anf4xLSkLHiGTg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2602,6 +2635,10 @@ packages: oxc-resolver@11.18.0: resolution: {integrity: sha512-Fv/b05AfhpYoCDvsog6tgsDm2yIwIeJafpMFLncNwKHRYu+Y1xQu5Q/rgUn7xBfuhNgjtPO7C0jCf7p2fLDj1g==} + oxlint-tsgolint@0.15.0: + resolution: {integrity: sha512-iwvFmhKQVZzVTFygUVI4t2S/VKEm+Mqkw3jQRJwfDuTcUYI5LCIYzdO5Dbuv4mFOkXZCcXaRRh0m+uydB5xdqw==} + hasBin: true + oxlint@1.50.0: resolution: {integrity: sha512-iSJ4IZEICBma8cZX7kxIIz9PzsYLF2FaLAYN6RKu7VwRVKdu7RIgpP99bTZaGl//Yao7fsaGZLSEo5xBrI5ReQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3772,6 +3809,24 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.18.0': optional: true + '@oxlint-tsgolint/darwin-arm64@0.15.0': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.15.0': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.15.0': + optional: true + + '@oxlint-tsgolint/linux-x64@0.15.0': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.15.0': + optional: true + + '@oxlint-tsgolint/win32-x64@0.15.0': + optional: true + '@oxlint/binding-android-arm-eabi@1.50.0': optional: true @@ -5168,7 +5223,16 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.18.0 '@oxc-resolver/binding-win32-x64-msvc': 11.18.0 - oxlint@1.50.0: + oxlint-tsgolint@0.15.0: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.15.0 + '@oxlint-tsgolint/darwin-x64': 0.15.0 + '@oxlint-tsgolint/linux-arm64': 0.15.0 + '@oxlint-tsgolint/linux-x64': 0.15.0 + '@oxlint-tsgolint/win32-arm64': 0.15.0 + '@oxlint-tsgolint/win32-x64': 0.15.0 + + oxlint@1.50.0(oxlint-tsgolint@0.15.0): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.50.0 '@oxlint/binding-android-arm64': 1.50.0 @@ -5189,6 +5253,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.50.0 '@oxlint/binding-win32-ia32-msvc': 1.50.0 '@oxlint/binding-win32-x64-msvc': 1.50.0 + oxlint-tsgolint: 0.15.0 parse5-htmlparser2-tree-adapter@7.1.0: dependencies: diff --git a/src/client/components/DefaultCatchBoundary.tsx b/src/client/components/DefaultCatchBoundary.tsx index 96f27a9..3a73622 100644 --- a/src/client/components/DefaultCatchBoundary.tsx +++ b/src/client/components/DefaultCatchBoundary.tsx @@ -26,7 +26,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) { { - router.invalidate(); + void router.invalidate(); }} /> @@ -39,7 +39,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {