Refactor ESLint configuration and update TypeScript dependencies for improved type safety

- Enhanced ESLint configuration to better support TypeScript file patterns and added centralized dependency versions using pnpm catalogs.
- Updated TypeScript configurations across applications to utilize new file structure and improved type inference with Zod.
- Refactored domain modules to replace deprecated type inference methods, ensuring better type safety and consistency.
- Cleaned up package.json files to standardize dependency versions and improve overall project maintainability.
This commit is contained in:
barsa 2025-12-12 14:50:12 +09:00
parent ece89de49a
commit 3f7fa02b83
18 changed files with 134 additions and 85 deletions

View File

@ -53,7 +53,7 @@
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"salesforce-pubsub-api-client": "^5.5.1", "salesforce-pubsub-api-client": "^5.5.1",
"ssh2-sftp-client": "^12.0.1", "ssh2-sftp-client": "^12.0.1",
"zod": "4.1.13" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^11.0.14", "@nestjs/cli": "^11.0.14",
@ -65,6 +65,6 @@
"@types/ssh2-sftp-client": "^9.0.6", "@types/ssh2-sftp-client": "^9.0.6",
"pino-pretty": "^13.1.3", "pino-pretty": "^13.1.3",
"tsc-alias": "^1.8.16", "tsc-alias": "^1.8.16",
"typescript": "5.9.3" "typescript": "catalog:"
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.node.json",
"compilerOptions": { "compilerOptions": {
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

View File

@ -29,7 +29,7 @@
"react-dom": "19.2.1", "react-dom": "19.2.1",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"world-countries": "^5.1.0", "world-countries": "^5.1.0",
"zod": "4.1.13", "zod": "catalog:",
"zustand": "^5.0.9" "zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
@ -39,6 +39,6 @@
"@types/react": "^19.2.7", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"typescript": "5.9.3" "typescript": "catalog:"
} }
} }

View File

@ -1,6 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/* eslint-env node */ /* eslint-env node */
/* global console, process */
/** /**
* Bundle size monitoring script * Bundle size monitoring script

View File

@ -5,7 +5,6 @@
import { mkdirSync, existsSync, writeFileSync, rmSync } from "fs"; import { mkdirSync, existsSync, writeFileSync, rmSync } from "fs";
import { join } from "path"; import { join } from "path";
import { URL } from "node:url"; import { URL } from "node:url";
/* global console */
const root = new URL("..", import.meta.url).pathname; // apps/portal const root = new URL("..", import.meta.url).pathname; // apps/portal
const nextDir = join(root, ".next"); const nextDir = join(root, ".next");

View File

@ -1,6 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
/* eslint-env node */ /* eslint-env node */
/* global __dirname, console, process */
const fs = require("node:fs"); const fs = require("node:fs");
const path = require("node:path"); const path = require("node:path");

View File

@ -1,12 +1,6 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.next.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"noEmit": true,
"plugins": [{ "name": "next" }], "plugins": [{ "name": "next" }],
"composite": true, "composite": true,
"tsBuildInfoFile": ".typecheck/tsconfig.tsbuildinfo", "tsBuildInfoFile": ".typecheck/tsconfig.tsbuildinfo",

View File

@ -4,12 +4,24 @@ import process from "node:process";
import js from "@eslint/js"; import js from "@eslint/js";
import nextPlugin from "@next/eslint-plugin-next"; import nextPlugin from "@next/eslint-plugin-next";
import reactHooks from "eslint-plugin-react-hooks"; import reactHooks from "eslint-plugin-react-hooks";
import tseslint from "typescript-eslint";
import prettier from "eslint-plugin-prettier";
import globals from "globals"; 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,
}));
export default [ export default [
// =============================================================================
// Global ignores // Global ignores
// =============================================================================
{ {
ignores: [ ignores: [
"**/node_modules/**", "**/node_modules/**",
@ -26,30 +38,22 @@ export default [
], ],
}, },
// Base JS recommendations // =============================================================================
// Base JS
// =============================================================================
js.configs.recommended, js.configs.recommended,
// Prettier integration // =============================================================================
{ // TypeScript (fast rules, no type information)
plugins: { prettier }, // =============================================================================
rules: { "prettier/prettier": "warn" }, ...withFiles(tseslint.configs.recommended, TS_FILES),
},
// TypeScript recommended rules (fast, no type info) // =============================================================================
...tseslint.configs.recommended.map((config) => ({ // TypeScript (type-aware rules) — only where we want the cost
// =============================================================================
...tseslint.configs.recommendedTypeChecked.map(config => ({
...config, ...config,
files: ["**/*.{ts,tsx}"], files: [...BFF_TS_FILES, ...PACKAGES_TS_FILES],
languageOptions: {
...(config.languageOptions || {}),
// Keep config simple: allow both environments; app-specific blocks can tighten later
globals: { ...globals.browser, ...globals.node },
},
})),
// TypeScript type-aware rules only where we really want them (backend + shared packages)
...tseslint.configs.recommendedTypeChecked.map((config) => ({
...config,
files: ["apps/bff/**/*.ts", "packages/**/*.ts"],
languageOptions: { languageOptions: {
...(config.languageOptions || {}), ...(config.languageOptions || {}),
parserOptions: { parserOptions: {
@ -57,13 +61,14 @@ export default [
projectService: true, projectService: true,
tsconfigRootDir: process.cwd(), tsconfigRootDir: process.cwd(),
}, },
globals: { ...globals.node },
}, },
})), })),
// Backend & domain packages // =============================================================================
// Backend + domain packages: sensible defaults
// =============================================================================
{ {
files: ["apps/bff/**/*.ts", "packages/domain/**/*.ts"], files: [...BFF_TS_FILES, "packages/domain/**/*.ts"],
rules: { rules: {
"@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
@ -71,9 +76,11 @@ export default [
}, },
}, },
// Strict rules for shared packages // =============================================================================
// Shared packages: stricter safety
// =============================================================================
{ {
files: ["packages/**/*.ts"], files: PACKAGES_TS_FILES,
rules: { rules: {
"@typescript-eslint/no-redundant-type-constituents": "error", "@typescript-eslint/no-redundant-type-constituents": "error",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
@ -83,13 +90,14 @@ export default [
}, },
}, },
// Portal (Next.js) rules (flat-config friendly) // =============================================================================
// Portal (Next.js)
// =============================================================================
{ {
...nextPlugin.configs["core-web-vitals"], ...nextPlugin.configs["core-web-vitals"],
files: ["apps/portal/**/*.{js,jsx,ts,tsx}"], files: PORTAL_FILES,
}, },
{ {
// Keep this minimal (defaults-first): only the two classic hook rules.
files: ["apps/portal/**/*.{jsx,tsx}"], files: ["apps/portal/**/*.{jsx,tsx}"],
plugins: { "react-hooks": reactHooks }, plugins: { "react-hooks": reactHooks },
rules: { rules: {
@ -97,23 +105,23 @@ export default [
"react-hooks/exhaustive-deps": "warn", "react-hooks/exhaustive-deps": "warn",
}, },
}, },
// Portal overrides
{ {
files: ["apps/portal/**/*.{ts,tsx}"], files: ["apps/portal/**/*.{ts,tsx}"],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
},
rules: { rules: {
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": [
"error", "error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
], ],
"@next/next/no-html-link-for-pages": "off", "@next/next/no-html-link-for-pages": "off",
}, },
}, },
// Prevent hard navigation in authenticated pages // =============================================================================
// Portal navigation guardrails
// =============================================================================
{ {
files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"], files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"],
rules: { rules: {
@ -126,8 +134,6 @@ export default [
], ],
}, },
}, },
// Exceptions for specific files
{ {
files: ["apps/portal/src/app/(authenticated)/layout.tsx"], files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
rules: { "no-restricted-imports": "off" }, rules: { "no-restricted-imports": "off" },
@ -137,7 +143,9 @@ export default [
rules: { "no-restricted-syntax": "off" }, rules: { "no-restricted-syntax": "off" },
}, },
// Enforce domain imports architecture // =============================================================================
// Architecture: import boundaries
// =============================================================================
{ {
files: ["apps/portal/src/**/*.{ts,tsx}", "apps/bff/src/**/*.ts"], files: ["apps/portal/src/**/*.{ts,tsx}", "apps/bff/src/**/*.ts"],
rules: { rules: {
@ -155,9 +163,11 @@ export default [
}, },
}, },
// BFF strict type safety // =============================================================================
// BFF: stricter type safety (type-aware)
// =============================================================================
{ {
files: ["apps/bff/**/*.ts"], files: BFF_TS_FILES,
rules: { rules: {
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-assignment": "error",
@ -170,13 +180,16 @@ export default [
}, },
}, },
// Node globals for config files // =============================================================================
// Node globals for tooling/config files
// =============================================================================
{ {
files: [ files: [
"*.config.*", "*.config.*",
"apps/portal/next.config.mjs", "apps/portal/next.config.mjs",
"config/**/*.{js,cjs,mjs}", "config/**/*.{js,cjs,mjs}",
"scripts/**/*.{js,cjs,mjs}", "scripts/**/*.{js,cjs,mjs}",
"apps/**/scripts/**/*.{js,cjs,mjs}",
], ],
languageOptions: { languageOptions: {
globals: { ...globals.node }, globals: { ...globals.node },

View File

@ -53,24 +53,20 @@
"devDependencies": { "devDependencies": {
"@next/eslint-plugin-next": "16.0.9", "@next/eslint-plugin-next": "16.0.9",
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@types/node": "^24.10.3", "@types/node": "catalog:",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"lint-staged": "^16.2.7", "lint-staged": "^16.2.7",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"globals": "^16.5.0", "globals": "^16.5.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.9.3", "typescript": "catalog:",
"typescript-eslint": "^8.49.0" "typescript-eslint": "^8.49.0"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"js-yaml": ">=4.1.1", "js-yaml": ">=4.1.1"
"typescript": "5.9.3",
"@types/node": "24.10.3",
"zod": "4.1.13"
} }
} }
} }

View File

@ -1,4 +1,4 @@
import type { infer as ZodInfer } from "zod"; import type { z } from "zod";
import type { import type {
activityTypeSchema, activityTypeSchema,
activitySchema, activitySchema,
@ -11,12 +11,12 @@ import type {
dashboardSummaryResponseSchema, dashboardSummaryResponseSchema,
} from "./schema.js"; } from "./schema.js";
export type ActivityType = ZodInfer<typeof activityTypeSchema>; export type ActivityType = z.infer<typeof activityTypeSchema>;
export type Activity = ZodInfer<typeof activitySchema>; export type Activity = z.infer<typeof activitySchema>;
export type DashboardStats = ZodInfer<typeof dashboardStatsSchema>; export type DashboardStats = z.infer<typeof dashboardStatsSchema>;
export type NextInvoice = ZodInfer<typeof nextInvoiceSchema>; export type NextInvoice = z.infer<typeof nextInvoiceSchema>;
export type DashboardSummary = ZodInfer<typeof dashboardSummarySchema>; export type DashboardSummary = z.infer<typeof dashboardSummarySchema>;
export type DashboardError = ZodInfer<typeof dashboardErrorSchema>; export type DashboardError = z.infer<typeof dashboardErrorSchema>;
export type ActivityFilter = ZodInfer<typeof activityFilterSchema>; export type ActivityFilter = z.infer<typeof activityFilterSchema>;
export type ActivityFilterConfig = ZodInfer<typeof activityFilterConfigSchema>; export type ActivityFilterConfig = z.infer<typeof activityFilterConfigSchema>;
export type DashboardSummaryResponse = ZodInfer<typeof dashboardSummaryResponseSchema>; export type DashboardSummaryResponse = z.infer<typeof dashboardSummaryResponseSchema>;

View File

@ -5,7 +5,7 @@
* These rules represent domain logic and should be reusable across frontend/backend. * These rules represent domain logic and should be reusable across frontend/backend.
*/ */
import type { infer as ZodInfer } from "zod"; import type { z } from "zod";
import { orderBusinessValidationSchema } from "./schema.js"; import { orderBusinessValidationSchema } from "./schema.js";
// ============================================================================ // ============================================================================
@ -101,7 +101,7 @@ export const orderWithSkuValidationSchema = orderBusinessValidationSchema
path: ["skus"], path: ["skus"],
}); });
export type OrderWithSkuValidation = ZodInfer<typeof orderWithSkuValidationSchema>; export type OrderWithSkuValidation = z.infer<typeof orderWithSkuValidationSchema>;
// ============================================================================ // ============================================================================
// Validation Error Messages // Validation Error Messages

View File

@ -133,10 +133,10 @@
"typecheck": "pnpm run type-check" "typecheck": "pnpm run type-check"
}, },
"peerDependencies": { "peerDependencies": {
"zod": "4.1.13" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"typescript": "5.9.3", "typescript": "catalog:",
"zod": "4.1.13" "zod": "catalog:"
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.node.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"declaration": true, "declaration": true,

View File

@ -1,3 +1,9 @@
packages: packages:
- apps/* - apps/*
- packages/* - packages/*
# Centralized dependency versions (pnpm Catalogs)
catalog:
zod: "4.1.13"
typescript: "5.9.3"
"@types/node": "24.10.3"

View File

@ -65,6 +65,31 @@ if (hasDrift) {
} }
" "
# 2b. Ensure a single installed Zod version (lockfile-level)
echo "🧩 Checking for multiple installed Zod versions..."
node -e "
const fs = require('fs');
const lock = fs.readFileSync('pnpm-lock.yaml', 'utf8');
const versions = new Set();
const re = /\\n\\s{2}zod@([^:\\s]+):/g;
let m;
while ((m = re.exec(lock)) !== null) versions.add(m[1]);
if (versions.size === 0) {
console.log('⚠️ No zod entries found in lockfile (unexpected).');
process.exit(1);
}
if (versions.size > 1) {
console.log('❌ Multiple zod versions installed:');
[...versions].sort().forEach(v => console.log(' - zod@' + v));
console.log('\\n💡 Fix by aligning dependencies or adding a pnpm override for zod.');
process.exit(1);
}
console.log('✅ Single zod version installed:', 'zod@' + [...versions][0]);
"
# 3. Security audit # 3. Security audit
echo "🔒 Running security audit..." echo "🔒 Running security audit..."
if pnpm audit --audit-level moderate; then if pnpm audit --audit-level moderate; then

View File

@ -1,10 +1,6 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": { "compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true, "strict": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,

12
tsconfig.next.json Normal file
View File

@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "preserve",
"noEmit": true
}
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}