Assist_Design/eslint.config.mjs

298 lines
10 KiB
JavaScript
Raw Normal View History

2025-08-27 10:54:05 +09:00
/* eslint-env node */
import process from "node:process";
2025-08-22 17:02:49 +09:00
import js from "@eslint/js";
import nextPlugin from "@next/eslint-plugin-next";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals";
import tseslint from "typescript-eslint";
const TS_FILES = ["**/*.{ts,tsx}"];
const BFF_TS_FILES = ["apps/bff/**/*.ts"];
const PACKAGES_TS_FILES = ["packages/**/*.ts"];
const PORTAL_FILES = ["apps/portal/**/*.{js,jsx,ts,tsx}"];
const withFiles = (configs, files) =>
configs.map(config => ({
...config,
files,
}));
2025-08-22 17:02:49 +09:00
export default [
// =============================================================================
2025-08-22 17:02:49 +09:00
// Global ignores
// =============================================================================
2025-08-22 17:02:49 +09:00
{
ignores: [
"**/node_modules/**",
"**/dist/**",
"**/.next/**",
"**/.turbo/**",
"**/.cache/**",
"**/.pnpm-store/**",
"**/.typecheck/**",
2025-08-22 17:02:49 +09:00
"**/build/**",
"**/coverage/**",
"**/next-env.d.ts",
"**/prisma/**",
2025-08-22 17:02:49 +09:00
],
},
// =============================================================================
// Base JS
// =============================================================================
2025-08-22 17:02:49 +09:00
js.configs.recommended,
// =============================================================================
// TypeScript (fast rules, no type information)
// =============================================================================
...withFiles(tseslint.configs.recommended, TS_FILES),
// =============================================================================
// TypeScript (type-aware rules) — only where we want the cost
// =============================================================================
...tseslint.configs.recommendedTypeChecked.map(config => ({
...config,
files: [...BFF_TS_FILES, ...PACKAGES_TS_FILES],
languageOptions: {
...(config.languageOptions || {}),
parserOptions: {
...((config.languageOptions && config.languageOptions.parserOptions) || {}),
projectService: true,
tsconfigRootDir: process.cwd(),
},
},
2025-08-22 17:02:49 +09:00
})),
// =============================================================================
// Backend + domain packages: sensible defaults
// =============================================================================
2025-08-22 17:02:49 +09:00
{
files: ["packages/domain/**/*.ts"],
2025-08-22 17:02:49 +09:00
rules: {
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
2025-08-22 17:02:49 +09:00
"no-console": ["warn", { allow: ["warn", "error"] }],
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["#toolkit/whmcs", "#toolkit/whmcs/*"],
message:
"WHMCS provider utilities must not live under toolkit. Keep them under `packages/domain/common/providers/whmcs-utils/` and import via relative paths inside the domain package.",
},
],
},
],
2025-08-22 17:02:49 +09:00
},
},
// =============================================================================
// Shared packages: stricter safety
// =============================================================================
2025-08-22 17:02:49 +09:00
{
files: PACKAGES_TS_FILES,
2025-08-22 17:02:49 +09:00
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",
},
},
// =============================================================================
// Portal (Next.js)
// =============================================================================
{
...nextPlugin.configs["core-web-vitals"],
files: PORTAL_FILES,
},
{
files: ["apps/portal/**/*.{jsx,tsx}"],
plugins: { "react-hooks": reactHooks },
rules: {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
},
},
2025-08-22 17:02:49 +09:00
{
files: ["apps/portal/**/*.{ts,tsx}"],
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@next/next/no-html-link-for-pages": "off",
},
2025-08-22 17:02:49 +09:00
},
// =============================================================================
// Portal navigation guardrails
// =============================================================================
{
files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "MemberExpression[object.name='window'][property.name='location']",
message: "Use next/link or useRouter for navigation.",
},
],
},
},
{
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
rules: { "no-restricted-imports": "off" },
},
{
files: ["apps/portal/src/app/(authenticated)/billing/invoices/[id]/page.tsx"],
rules: { "no-restricted-syntax": "off" },
},
// =============================================================================
// Architecture: import boundaries
// =============================================================================
{
files: ["apps/portal/src/**/*.{ts,tsx}", "apps/bff/src/**/*.ts"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@customer-portal/domain",
message:
"Do not import @customer-portal/domain (root). Use @customer-portal/domain/<module> instead.",
},
],
patterns: [
{
group: ["@customer-portal/domain/**/src/**"],
message: "Import from @customer-portal/domain/<module> instead of internals.",
},
{
group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"],
message:
"No deep @customer-portal/domain imports. Use @customer-portal/domain/<module> (or BFF-only: .../<module>/providers).",
},
{
group: ["@customer-portal/domain/*/providers/*"],
message:
"Do not deep-import provider internals. Import from @customer-portal/domain/<module>/providers only.",
},
],
},
],
},
},
// =============================================================================
// Portal: hard boundary — must not import provider adapters/types
// =============================================================================
{
files: ["apps/portal/src/**/*.{ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "@customer-portal/domain",
message:
"Do not import @customer-portal/domain (root). Use @customer-portal/domain/<module> instead.",
},
],
patterns: [
{
group: ["@/hooks/*"],
message:
"Do not import from @/hooks. Use @/lib/hooks (app-level hooks) or feature hooks under src/features/<feature>/hooks.",
},
{
group: ["@customer-portal/domain/**/src/**"],
message: "Import from @customer-portal/domain/<module> instead of internals.",
},
{
group: ["@customer-portal/domain/*/providers"],
message:
"Portal must not import provider adapters/types. Import normalized domain models from @customer-portal/domain/<module> instead.",
},
{
group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"],
message: "No deep @customer-portal/domain imports. Use @customer-portal/domain/<module> only.",
},
{
group: ["@customer-portal/domain/*/providers/*"],
message:
"Do not deep-import provider internals. Import from @customer-portal/domain/<module>/providers only (BFF-only).",
},
],
},
],
2025-08-22 17:02:49 +09:00
},
},
{
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
rules: { "no-restricted-imports": "off" },
},
2025-08-22 17:02:49 +09:00
// =============================================================================
// BFF: stricter type safety (type-aware)
// =============================================================================
2025-08-22 17:02:49 +09:00
{
files: BFF_TS_FILES,
2025-08-22 17:02:49 +09:00
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",
},
},
// =============================================================================
// BFF controllers: request DTOs must come from shared domain schemas (no inline zod)
// =============================================================================
{
files: ["apps/bff/src/modules/**/*.controller.ts"],
rules: {
"no-restricted-syntax": [
"error",
{
selector: "ImportDeclaration[source.value='zod']",
message:
"Do not import zod in controllers. Put request/param/query schemas in @customer-portal/domain and use createZodDto(schema) with the global ZodValidationPipe.",
},
],
},
},
// =============================================================================
// Node globals for tooling/config files
// =============================================================================
{
files: [
"*.config.*",
"apps/portal/next.config.mjs",
"config/**/*.{js,cjs,mjs}",
"scripts/**/*.{js,cjs,mjs}",
"apps/**/scripts/**/*.{js,cjs,mjs}",
],
languageOptions: {
globals: { ...globals.node },
2025-08-22 17:02:49 +09:00
},
},
];