213 lines
6.3 KiB
JavaScript
213 lines
6.3 KiB
JavaScript
/* eslint-env node */
|
|
import process from "node:process";
|
|
import js from "@eslint/js";
|
|
import tseslint from "typescript-eslint";
|
|
import prettier from "eslint-plugin-prettier";
|
|
import { FlatCompat } from "@eslint/eslintrc";
|
|
import path from "node:path";
|
|
import globals from "globals";
|
|
|
|
// Use FlatCompat to consume Next.js' legacy shareable configs under apps/portal
|
|
const compat = new FlatCompat({ baseDirectory: path.resolve("apps/portal") });
|
|
|
|
export default [
|
|
// Global ignores
|
|
{
|
|
ignores: [
|
|
"**/node_modules/**",
|
|
"**/dist/**",
|
|
"**/.next/**",
|
|
"**/build/**",
|
|
"**/coverage/**",
|
|
"**/next-env.d.ts",
|
|
],
|
|
},
|
|
|
|
// Base JS recommendations for all files
|
|
js.configs.recommended,
|
|
|
|
// Prettier integration (warn-only)
|
|
{
|
|
plugins: { prettier },
|
|
rules: {
|
|
"prettier/prettier": "warn",
|
|
},
|
|
},
|
|
|
|
// TypeScript (type-checked) for TS files only
|
|
...tseslint.configs.recommendedTypeChecked.map(config => ({
|
|
...config,
|
|
files: ["**/*.ts", "**/*.tsx"],
|
|
languageOptions: {
|
|
...(config.languageOptions || {}),
|
|
globals: {
|
|
...globals.node,
|
|
},
|
|
},
|
|
})),
|
|
{
|
|
files: [
|
|
"apps/bff/**/*.ts",
|
|
"packages/domain/**/*.ts",
|
|
"packages/logging/**/*.ts",
|
|
"packages/api-client/**/*.ts",
|
|
"packages/validation/**/*.ts"
|
|
],
|
|
languageOptions: {
|
|
parserOptions: {
|
|
projectService: true,
|
|
tsconfigRootDir: process.cwd(),
|
|
|
|
},
|
|
},
|
|
rules: {
|
|
"@typescript-eslint/consistent-type-imports": "error",
|
|
"@typescript-eslint/no-unused-vars": [
|
|
"warn",
|
|
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
|
],
|
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
},
|
|
},
|
|
|
|
// Enforce consistent strict rules across shared as well
|
|
{
|
|
files: ["packages/shared/**/*.ts"],
|
|
rules: {
|
|
"@typescript-eslint/no-redundant-type-constituents": "error",
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/no-unsafe-return": "error",
|
|
"@typescript-eslint/no-unsafe-call": "error",
|
|
"@typescript-eslint/no-unsafe-member-access": "error",
|
|
},
|
|
},
|
|
|
|
// Next.js app: apply Next's recommended config; TS rules only on TS files
|
|
...compat
|
|
.extends("next/core-web-vitals")
|
|
.map(config => ({ ...config, files: ["apps/portal/**/*.{js,jsx,ts,tsx}"] })),
|
|
...compat
|
|
.extends("next/typescript")
|
|
.map(config => ({ ...config, files: ["apps/portal/**/*.{ts,tsx}"] })),
|
|
|
|
// Ensure type-aware rules in portal have parser services
|
|
{
|
|
files: ["apps/portal/**/*.{ts,tsx}"],
|
|
languageOptions: {
|
|
parserOptions: {
|
|
projectService: true,
|
|
tsconfigRootDir: process.cwd(),
|
|
},
|
|
},
|
|
rules: {
|
|
"@next/next/no-html-link-for-pages": "off",
|
|
},
|
|
},
|
|
|
|
// Prevent importing the DashboardLayout directly in (authenticated) pages.
|
|
// Pages should rely on the shared route-group layout at (authenticated)/layout.tsx.
|
|
{
|
|
files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["@/components/layout/dashboard-layout"],
|
|
message:
|
|
"Use the shared (authenticated)/layout.tsx instead of importing DashboardLayout in pages.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
// Prefer Next.js <Link> and router, forbid window/location hard reload in portal pages
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
selector: "MemberExpression[object.name='window'][property.name='location']",
|
|
message: "Use next/link or useRouter for navigation, not window.location.",
|
|
},
|
|
{
|
|
selector:
|
|
"MemberExpression[object.name='location'][property.name=/^(href|assign|replace)$/]",
|
|
message: "Use next/link or useRouter for navigation, not location.*.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
// Allow the shared layout file itself to import the layout component
|
|
{
|
|
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
|
|
rules: {
|
|
"no-restricted-imports": "off",
|
|
},
|
|
},
|
|
// Allow controlled window.location usage for invoice SSO download
|
|
{
|
|
files: ["apps/portal/src/app/(authenticated)/billing/invoices/[id]/page.tsx"],
|
|
rules: {
|
|
"no-restricted-syntax": "off",
|
|
},
|
|
},
|
|
|
|
// Prevent type duplication and enforce modern patterns
|
|
{
|
|
files: ["apps/portal/src/**/*.{ts,tsx}", "packages/domain/src/**/*.ts"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["**/utils/ui-state*"],
|
|
message: "ui-state.ts has been removed. Use patterns from @customer-portal/domain instead.",
|
|
},
|
|
{
|
|
group: ["@/types"],
|
|
message: "Avoid importing from @/types. Import types directly from @customer-portal/domain or define locally.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
// Prevent defining deprecated type patterns
|
|
"no-restricted-syntax": [
|
|
"error",
|
|
{
|
|
selector: "TSInterfaceDeclaration[id.name=/^(LegacyAsyncState|PaginatedState|FilteredState)$/]",
|
|
message: "These legacy state types are deprecated. Use AsyncState, PaginatedAsyncState, or FilterState from @customer-portal/domain instead.",
|
|
},
|
|
{
|
|
selector: "TSTypeAliasDeclaration[id.name=/^(LegacyAsyncState|PaginatedState|FilteredState)$/]",
|
|
message: "These legacy state types are deprecated. Use AsyncState, PaginatedAsyncState, or FilterState from @customer-portal/domain instead.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// Node globals for Next config file
|
|
{
|
|
files: ["apps/portal/next.config.mjs"],
|
|
languageOptions: {
|
|
globals: {
|
|
...globals.node,
|
|
},
|
|
},
|
|
},
|
|
|
|
// BFF: strict rules enforced
|
|
{
|
|
files: ["apps/bff/**/*.ts"],
|
|
rules: {
|
|
"@typescript-eslint/no-explicit-any": "error",
|
|
"@typescript-eslint/no-unsafe-assignment": "error",
|
|
"@typescript-eslint/no-unsafe-member-access": "error",
|
|
"@typescript-eslint/no-unsafe-call": "error",
|
|
"@typescript-eslint/no-unsafe-return": "error",
|
|
"@typescript-eslint/no-unsafe-argument": "error",
|
|
"@typescript-eslint/require-await": "error",
|
|
"@typescript-eslint/no-floating-promises": "error",
|
|
},
|
|
},
|
|
];
|