diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 00ca623e..0e2225ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,12 +57,12 @@ jobs: - name: Build server (tsc -> dist) run: cd server && npm run build - - name: Typecheck (informational) - # Pre-existing type errors in the NestJS rewrite; surfaces them without - # blocking CI. Ratchet to blocking once the legacy code is cleaned up. - continue-on-error: true + - name: Typecheck run: cd server && npm run typecheck + - name: Lint + run: cd server && npm run lint:check + - name: Run tests run: cd server && npm run test:coverage @@ -93,6 +93,15 @@ jobs: - name: Build shared run: npm run build --workspace=shared + - name: Typecheck + run: cd client && npm run typecheck + + - name: Lint + run: cd client && npm run lint:check + + - name: Page pattern check + run: cd client && npm run lint:pages + - name: Run tests run: cd client && npm run test:coverage diff --git a/client/eslint.config.mjs b/client/eslint.config.mjs new file mode 100644 index 00000000..6e48d7e4 --- /dev/null +++ b/client/eslint.config.mjs @@ -0,0 +1,78 @@ +import js from '@eslint/js'; + +import gitignore from 'eslint-config-flat-gitignore'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +// Minimal stub so the existing `// eslint-disable-next-line react/no-danger` +// directive in src/i18n/TransHtml.tsx resolves without pulling in the full +// eslint-plugin-react (not a dependency here). The rule is a no-op. +const reactStub = { + rules: { + 'no-danger': { + meta: { schema: [] }, + create() { + return {}; + }, + }, + }, +}; + +export default tseslint.config( + gitignore({ strict: false }), + { + ignores: [ + 'node_modules', + 'dist', + 'coverage', + 'public', + 'test-results', + 'playwright-report', + 'e2e/**', + 'scripts/**', + '**/*.config.js', + '**/*.config.ts', + '**/*.config.mjs', + ], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + { + files: ['src/**/*.{ts,tsx}', 'tests/**/*.{ts,tsx}'], + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + react: reactStub, + }, + rules: { + 'react/no-danger': 'off', + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + + // --- Severities tuned to keep CI green on a codebase that was never linted --- + // (each rule below has pre-existing violations; surfaced as warnings, not blockers) + + // rules-of-hooks has one conditional-hook violation in PlaceInspector.tsx -> warn (not error). + 'react-hooks/rules-of-hooks': 'warn', + 'react-hooks/exhaustive-deps': 'warn', + + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-unused-expressions': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + '@typescript-eslint/no-this-alias': 'warn', + '@typescript-eslint/no-non-null-asserted-optional-chain': 'warn', + + // js.recommended rules with pre-existing hits. + 'no-empty': 'warn', + 'no-useless-escape': 'warn', + 'no-useless-assignment': 'warn', + 'preserve-caught-error': 'warn', + }, + }, +); diff --git a/client/package.json b/client/package.json index 670743cc..44c8dc55 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "test:watch": "vitest", "test:coverage": "vitest run --coverage", "lint": "eslint .", + "lint:check": "eslint .", "lint:pages": "node scripts/check-page-pattern.mjs", "e2e": "playwright test", "e2e:report": "playwright show-report", @@ -47,6 +48,7 @@ "zustand": "^4.5.2" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@playwright/test": "^1.60.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -61,6 +63,7 @@ "autoprefixer": "^10.4.18", "eslint": "^10.2.1", "eslint-config-flat-gitignore": "^2.3.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "fake-indexeddb": "^6.2.5", @@ -73,6 +76,7 @@ "sharp": "^0.33.0", "tailwindcss": "^3.4.1", "typescript": "^6.0.2", + "typescript-eslint": "^8.58.2", "vite": "^5.1.4", "vite-plugin-pwa": "^0.21.0", "vitest": "^3.2.4" diff --git a/package-lock.json b/package-lock.json index de512356..a124db8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "zustand": "^4.5.2" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@playwright/test": "^1.60.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.2", @@ -65,6 +66,7 @@ "autoprefixer": "^10.4.18", "eslint": "^10.2.1", "eslint-config-flat-gitignore": "^2.3.0", + "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", "fake-indexeddb": "^6.2.5", @@ -77,6 +79,7 @@ "sharp": "^0.33.0", "tailwindcss": "^3.4.1", "typescript": "^6.0.2", + "typescript-eslint": "^8.58.2", "vite": "^5.1.4", "vite-plugin-pwa": "^0.21.0", "vitest": "^3.2.4" @@ -17353,6 +17356,7 @@ "zod": "^4.3.6" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@nestjs/testing": "^11.1.24", "@swc/core": "^1.15.40", "@trivago/prettier-plugin-sort-imports": "^6.0.2", @@ -17382,6 +17386,7 @@ "prettier": "^3.8.3", "prettier-plugin-organize-imports": "^4.3.0", "supertest": "^7.2.2", + "typescript-eslint": "^8.58.2", "tz-lookup": "^6.1.25", "unplugin-swc": "^1.5.9", "vitest": "^3.2.4" diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs new file mode 100644 index 00000000..08fbd59e --- /dev/null +++ b/server/eslint.config.mjs @@ -0,0 +1,47 @@ +import js from '@eslint/js'; + +import gitignore from 'eslint-config-flat-gitignore'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + gitignore({ strict: false }), + { + ignores: [ + 'node_modules', + 'dist', + 'coverage', + 'public', + 'data', + 'uploads', + 'assets', + 'scripts/**', + 'reset-admin.js', + '**/*.config.js', + '**/*.config.ts', + '**/*.config.mjs', + ], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + { + files: ['src/**/*.ts', 'tests/**/*.ts'], + rules: { + // --- Severities tuned to keep CI green on a codebase that was never linted --- + // (each rule below has pre-existing violations; surfaced as warnings, not blockers) + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + // The server is CommonJS (tsconfig module: commonjs); require() is intentional throughout. + '@typescript-eslint/no-require-imports': 'warn', + '@typescript-eslint/no-unsafe-function-type': 'warn', + // js.recommended rules with pre-existing hits in the never-linted codebase. + 'no-empty': 'warn', + 'no-useless-escape': 'warn', + 'prefer-const': 'warn', + }, + }, +); diff --git a/server/package.json b/server/package.json index 0c730e65..a2b403a9 100644 --- a/server/package.json +++ b/server/package.json @@ -11,6 +11,7 @@ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "lint:check": "eslint .", "test": "vitest run", "test:watch": "vitest", "test:unit": "vitest run tests/unit", @@ -64,6 +65,7 @@ "file-type": "^21.3.4" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "eslint-config-prettier": "^10.0.1", "eslint-plugin-prettier": "^5.2.2", @@ -71,6 +73,7 @@ "prettier-plugin-organize-imports": "^4.3.0", "eslint": "^9.18.0", "eslint-config-flat-gitignore": "^2.3.0", + "typescript-eslint": "^8.58.2", "@nestjs/testing": "^11.1.24", "@swc/core": "^1.15.40", "@types/archiver": "^7.0.0",