Update ESLint configuration, package dependencies, and improve Prisma integration
- Refactored ESLint configuration for better clarity and organization, including updates to TypeScript rules and Next.js app settings. - Upgraded package dependencies, including Next.js to version 16.0.8 and Prisma to version 7.1.0, enhancing performance and compatibility. - Modified Dockerfile for BFF to reflect updated Prisma version and optimize build settings. - Improved Prisma service to utilize PostgreSQL connection pooling with the new PrismaPg adapter, ensuring better database management. - Cleaned up TypeScript configuration files for consistency and updated module settings to align with ESNext standards. - Adjusted pre-commit script to streamline security audits and removed unnecessary linting during development.
This commit is contained in:
parent
f30dcc0608
commit
89b495db1a
@ -1,21 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Run type checking
|
||||
pnpm type-check
|
||||
|
||||
# Linting disabled during active development phase
|
||||
# TODO: Re-enable before production release
|
||||
# pnpm lint
|
||||
|
||||
# Quick security check (only fail on high/critical vulnerabilities)
|
||||
echo "🔒 Running security audit..."
|
||||
echo "Running security audit..."
|
||||
if ! pnpm audit --audit-level=high > /dev/null 2>&1; then
|
||||
echo ""
|
||||
echo "⚠️ High or critical security vulnerabilities detected!"
|
||||
echo "Run 'pnpm audit' to see details and 'pnpm update' to fix."
|
||||
echo ""
|
||||
# Uncomment the line below to block commits with vulnerabilities:
|
||||
# exit 1
|
||||
echo "High or critical security vulnerabilities detected!"
|
||||
echo "Run 'pnpm audit' to see details."
|
||||
fi
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
# =============================================================================
|
||||
|
||||
ARG NODE_VERSION=22
|
||||
ARG PNPM_VERSION=10.15.0
|
||||
ARG PRISMA_VERSION=6.16.0
|
||||
ARG PNPM_VERSION=10.25.0
|
||||
ARG PRISMA_VERSION=7.1.0
|
||||
|
||||
# =============================================================================
|
||||
# Stage 1: Builder
|
||||
|
||||
@ -40,9 +40,9 @@
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.1.9",
|
||||
"@nestjs/throttler": "^6.5.0",
|
||||
"@prisma/client": "^6.19.0",
|
||||
"@prisma/adapter-pg": "^7.1.0",
|
||||
"@prisma/client": "^7.1.0",
|
||||
"@sendgrid/mail": "^8.1.6",
|
||||
"@types/ssh2-sftp-client": "^9.0.6",
|
||||
"bcrypt": "^6.0.0",
|
||||
"bullmq": "^5.65.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
@ -57,6 +57,7 @@
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.16.3",
|
||||
"pino": "^10.1.0",
|
||||
"pino-http": "^11.0.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
@ -80,18 +81,18 @@
|
||||
"@types/node": "^24.10.2",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/pg": "^8.15.6",
|
||||
"@types/ssh2-sftp-client": "^9.0.6",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"jest": "^30.2.0",
|
||||
"prisma": "^6.19.0",
|
||||
"prisma": "^7.1.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.1.4",
|
||||
"ts-jest": "^29.4.6",
|
||||
"ts-loader": "^9.5.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.21.0",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-transform-paths": "^3.5.5"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
|
||||
29
apps/bff/prisma/prisma.config.ts
Normal file
29
apps/bff/prisma/prisma.config.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { defineConfig } from "prisma";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { Pool } from "pg";
|
||||
|
||||
/**
|
||||
* Prisma 7 Configuration
|
||||
*
|
||||
* This configuration file is required for Prisma 7+ where the datasource URL
|
||||
* is no longer specified in schema.prisma. Instead, connection configuration
|
||||
* is provided here for migrations and in the PrismaClient constructor for runtime.
|
||||
*
|
||||
* @see https://pris.ly/d/config-datasource
|
||||
* @see https://pris.ly/d/prisma7-client-config
|
||||
*/
|
||||
export default defineConfig({
|
||||
earlyAccess: true,
|
||||
schema: "./schema.prisma",
|
||||
migrate: {
|
||||
adapter: async () => {
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL environment variable is required for migrations");
|
||||
}
|
||||
const pool = new Pool({ connectionString });
|
||||
return new PrismaPg(pool);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -15,7 +15,8 @@ generator client {
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
// Note: Prisma 7+ requires connection URL to be passed via adapter in PrismaClient constructor
|
||||
// See prisma.config.ts for migration configuration
|
||||
}
|
||||
|
||||
model User {
|
||||
|
||||
@ -1,13 +1,54 @@
|
||||
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
|
||||
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from "@nestjs/common";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { Pool } from "pg";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
|
||||
/**
|
||||
* Prisma Service
|
||||
*
|
||||
* Prisma 7+ requires passing an adapter for database connections instead of
|
||||
* specifying the URL in the schema file. This service creates a PostgreSQL
|
||||
* connection pool and passes it to the PrismaClient via the PrismaPg adapter.
|
||||
*
|
||||
* @see https://pris.ly/d/prisma7-client-config
|
||||
*/
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly logger = new Logger(PrismaService.name);
|
||||
private readonly pool: Pool;
|
||||
|
||||
constructor() {
|
||||
const connectionString = process.env.DATABASE_URL;
|
||||
if (!connectionString) {
|
||||
throw new Error("DATABASE_URL environment variable is required");
|
||||
}
|
||||
|
||||
// Create a connection pool for PostgreSQL
|
||||
const pool = new Pool({
|
||||
connectionString,
|
||||
// Connection pool settings optimized for NestJS
|
||||
max: 10, // Maximum number of clients in the pool
|
||||
idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
|
||||
connectionTimeoutMillis: 5000, // Return an error after 5 seconds if connection fails
|
||||
});
|
||||
|
||||
// Create the Prisma PostgreSQL adapter
|
||||
const adapter = new PrismaPg(pool);
|
||||
|
||||
// Initialize PrismaClient with the adapter
|
||||
super({ adapter });
|
||||
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
this.logger.log("Database connection established");
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
await this.pool.end();
|
||||
this.logger.log("Database connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,39 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
// NestJS specific settings
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictPropertyInitialization": false,
|
||||
|
||||
// Path mappings for clean imports
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@bff/core/*": ["src/core/*"],
|
||||
"@bff/infra/*": ["src/infra/*"],
|
||||
"@bff/modules/*": ["src/modules/*"],
|
||||
"@bff/integrations/*": ["src/integrations/*"],
|
||||
"@customer-portal/validation": ["../../packages/validation/dist/index"],
|
||||
"@customer-portal/validation/*": ["../../packages/validation/dist/*"]
|
||||
"@bff/*": ["src/*"]
|
||||
},
|
||||
"rootDirs": [
|
||||
"src",
|
||||
"../../packages/validation/dist",
|
||||
"../../packages/validation/src"
|
||||
],
|
||||
|
||||
// Type checking
|
||||
"noEmit": true,
|
||||
"types": ["node"],
|
||||
"typeRoots": ["./node_modules/@types"]
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"ts-node": {
|
||||
"transpileOnly": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
"module": "CommonJS"
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "scripts/**/*", "test/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"include": ["src/**/*", "test/**/*"],
|
||||
"exclude": ["node_modules", "dist", "prisma"]
|
||||
}
|
||||
|
||||
2
apps/portal/next-env.d.ts
vendored
2
apps/portal/next-env.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@ -1,111 +1,78 @@
|
||||
/* eslint-env node */
|
||||
import bundleAnalyzer from "@next/bundle-analyzer";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const workspaceRoot = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
});
|
||||
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// Enable standalone output only for production deployment
|
||||
output: process.env.NODE_ENV === "production" ? "standalone" : undefined,
|
||||
|
||||
// Ensure workspace packages are transpiled correctly
|
||||
transpilePackages: ["@customer-portal/domain", "@customer-portal/validation"],
|
||||
|
||||
// Tell Next to NOT bundle these server-only libs
|
||||
serverExternalPackages: [
|
||||
"pino",
|
||||
"pino-pretty",
|
||||
"pino-abstract-transport",
|
||||
"thread-stream",
|
||||
"sonic-boom",
|
||||
// Avoid flaky vendor-chunk resolution during dev for small utils
|
||||
"tailwind-merge",
|
||||
],
|
||||
|
||||
// Turbopack configuration (Next.js 15.5+)
|
||||
// Note: public turbopack options are limited; aliasing is handled via tsconfig/webpack resolutions
|
||||
turbopack: {
|
||||
resolveAlias: {
|
||||
"@customer-portal/domain": path.join(workspaceRoot, "packages/domain/dist"),
|
||||
},
|
||||
},
|
||||
|
||||
webpack(config) {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
"@customer-portal/domain": path.join(workspaceRoot, "packages/domain/dist"),
|
||||
};
|
||||
return config;
|
||||
},
|
||||
|
||||
// Environment variables validation
|
||||
env: {
|
||||
NEXT_PUBLIC_API_BASE: process.env.NEXT_PUBLIC_API_BASE,
|
||||
NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
|
||||
NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
|
||||
},
|
||||
|
||||
// Image optimization
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "**",
|
||||
},
|
||||
],
|
||||
remotePatterns: [{ protocol: "https", hostname: "**" }],
|
||||
},
|
||||
|
||||
// Disable ESLint blocking during production builds to avoid CI/CD failures
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
|
||||
// API rewrites for development - proxy /api/* to BFF
|
||||
// This is the standard Next.js pattern that mirrors production (nginx proxy)
|
||||
async rewrites() {
|
||||
// Only apply rewrites in development; production uses nginx
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return [
|
||||
{
|
||||
source: "/api/:path*",
|
||||
destination: "http://localhost:4000/api/:path*",
|
||||
},
|
||||
];
|
||||
return [{ source: "/api/:path*", destination: "http://localhost:4000/api/:path*" }];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
// Security headers
|
||||
async headers() {
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
return [
|
||||
{
|
||||
// Apply security headers to all routes
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "DENY",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
},
|
||||
{
|
||||
key: "X-XSS-Protection",
|
||||
value: "1; mode=block",
|
||||
},
|
||||
// Content Security Policy - allows Next.js inline scripts/styles
|
||||
{ key: "X-Frame-Options", value: "DENY" },
|
||||
{ key: "X-Content-Type-Options", value: "nosniff" },
|
||||
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
||||
{ key: "X-XSS-Protection", value: "1; mode=block" },
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: [
|
||||
"default-src 'self'",
|
||||
// Next.js requires unsafe-inline for hydration scripts
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
||||
// Next.js/Tailwind requires unsafe-inline for styles
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https:",
|
||||
"font-src 'self' data:",
|
||||
// Allow API connections (include localhost for development)
|
||||
// Allow localhost in development for API calls to BFF
|
||||
`connect-src 'self' https: ${process.env.NODE_ENV === "development" ? "http://localhost:*" : ""}`,
|
||||
`connect-src 'self' https:${isDev ? " http://localhost:*" : ""}`,
|
||||
"frame-ancestors 'none'",
|
||||
].join("; "),
|
||||
},
|
||||
@ -114,44 +81,15 @@ const nextConfig = {
|
||||
];
|
||||
},
|
||||
|
||||
// Production optimizations
|
||||
compiler: {
|
||||
// Remove console.logs in production
|
||||
removeConsole: process.env.NODE_ENV === "production",
|
||||
},
|
||||
|
||||
// Experimental flags
|
||||
experimental: {
|
||||
externalDir: true,
|
||||
optimizePackageImports: ["@heroicons/react", "lucide-react", "@tanstack/react-query"],
|
||||
optimizePackageImports: ["@heroicons/react", "@tanstack/react-query"],
|
||||
},
|
||||
|
||||
webpack(config) {
|
||||
const workspaceRoot = path.resolve(__dirname, "..", "..");
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
"@customer-portal/domain": path.join(workspaceRoot, "packages/domain"),
|
||||
"@customer-portal/validation": path.join(workspaceRoot, "packages/validation/src"),
|
||||
};
|
||||
const preferredExtensions = [".ts", ".tsx", ".mts", ".cts"];
|
||||
const existingExtensions = config.resolve.extensions || [];
|
||||
config.resolve.extensions = [...new Set([...preferredExtensions, ...existingExtensions])];
|
||||
config.resolve.extensionAlias = {
|
||||
...(config.resolve.extensionAlias || {}),
|
||||
".js": [".ts", ".tsx", ".js"],
|
||||
".mjs": [".mts", ".ts", ".tsx", ".mjs"],
|
||||
};
|
||||
config.module.rules.push({
|
||||
test: /packages\/domain\/.*\.js$/,
|
||||
type: "javascript/esm",
|
||||
});
|
||||
return config;
|
||||
},
|
||||
|
||||
// Keep type checking enabled; monorepo paths provide types
|
||||
typescript: { ignoreBuildErrors: false },
|
||||
|
||||
// Prefer Turbopack; no custom webpack override needed
|
||||
};
|
||||
|
||||
export default withBundleAnalyzer(nextConfig);
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"predev": "node ./scripts/dev-prep.mjs",
|
||||
"dev": "next dev -p ${NEXT_PORT:-3000}",
|
||||
"build": "next build",
|
||||
"build:turbo": "next build --turbopack",
|
||||
"build:webpack": "next build --webpack",
|
||||
"build:analyze": "ANALYZE=true next build",
|
||||
"analyze": "npm run build:analyze",
|
||||
"start": "next start -p ${NEXT_PORT:-3000}",
|
||||
@ -25,7 +25,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"next": "15.5.7",
|
||||
"next": "16.0.8",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
@ -35,7 +35,7 @@
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^15.5.7",
|
||||
"@next/bundle-analyzer": "^16.0.8",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@types/node": "^24.10.2",
|
||||
"@types/react": "^19.2.7",
|
||||
|
||||
@ -1,27 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "preserve",
|
||||
"noEmit": true,
|
||||
"moduleResolution": "node",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"plugins": [{ "name": "next" }],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/components/*": ["./src/components/*"],
|
||||
"@/core/*": ["./src/core/*"],
|
||||
"@/features/*": ["./src/features/*"],
|
||||
"@/shared/*": ["./src/shared/*"],
|
||||
"@/styles/*": ["./src/styles/*"],
|
||||
"@/types/*": ["./src/types/*"],
|
||||
"@/lib/*": ["./src/lib/*"],
|
||||
"@customer-portal/domain": ["../../packages/domain/index.ts"],
|
||||
"@customer-portal/domain/*": ["../../packages/domain/*"],
|
||||
"@customer-portal/validation": ["../../packages/validation/src"],
|
||||
"@customer-portal/validation/*": ["../../packages/validation/src/*"]
|
||||
},
|
||||
"allowJs": false
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
/* eslint-env node */
|
||||
import process from "node:process";
|
||||
import path from "node:path";
|
||||
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 [
|
||||
@ -23,35 +22,28 @@ export default [
|
||||
],
|
||||
},
|
||||
|
||||
// Base JS recommendations for all files
|
||||
// Base JS recommendations
|
||||
js.configs.recommended,
|
||||
|
||||
// Prettier integration (warn-only)
|
||||
// Prettier integration
|
||||
{
|
||||
plugins: { prettier },
|
||||
rules: {
|
||||
"prettier/prettier": "warn",
|
||||
},
|
||||
rules: { "prettier/prettier": "warn" },
|
||||
},
|
||||
|
||||
// TypeScript (type-checked) for TS files only
|
||||
...tseslint.configs.recommendedTypeChecked.map(config => ({
|
||||
// TypeScript type-checked rules for all TS files
|
||||
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
||||
...config,
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
...(config.languageOptions || {}),
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
})),
|
||||
|
||||
// Backend & domain packages
|
||||
{
|
||||
files: [
|
||||
"apps/bff/**/*.ts",
|
||||
"packages/domain/**/*.ts",
|
||||
"packages/logging/**/*.ts",
|
||||
"packages/validation/**/*.ts",
|
||||
],
|
||||
files: ["apps/bff/**/*.ts", "packages/domain/**/*.ts"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
@ -60,17 +52,14 @@ export default [
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||
},
|
||||
},
|
||||
|
||||
// Enforce consistent strict rules across shared as well
|
||||
// Strict rules for shared packages
|
||||
{
|
||||
files: ["packages/**/*.{ts,tsx}"],
|
||||
files: ["packages/**/*.ts"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-redundant-type-constituents": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
@ -80,15 +69,17 @@ export default [
|
||||
},
|
||||
},
|
||||
|
||||
// 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}"] })),
|
||||
// Next.js app config
|
||||
...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
|
||||
// Portal type-aware rules
|
||||
{
|
||||
files: ["apps/portal/**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
@ -102,41 +93,31 @@ export default [
|
||||
},
|
||||
},
|
||||
|
||||
// Authenticated pages should rely on the shared route-group layout at (authenticated)/layout.tsx.
|
||||
// Prevent hard navigation in authenticated pages
|
||||
{
|
||||
files: ["apps/portal/src/app/(authenticated)/**/*.{ts,tsx}"],
|
||||
rules: {
|
||||
// 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.*.",
|
||||
message: "Use next/link or useRouter for navigation.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Allow the shared layout file itself to import the layout component
|
||||
|
||||
// Exceptions for specific files
|
||||
{
|
||||
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
|
||||
rules: {
|
||||
"no-restricted-imports": "off",
|
||||
},
|
||||
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",
|
||||
},
|
||||
rules: { "no-restricted-syntax": "off" },
|
||||
},
|
||||
|
||||
// Enforce layered type system architecture
|
||||
// Enforce domain imports architecture
|
||||
{
|
||||
files: ["apps/portal/src/**/*.{ts,tsx}", "apps/bff/src/**/*.ts"],
|
||||
rules: {
|
||||
@ -146,52 +127,15 @@ export default [
|
||||
patterns: [
|
||||
{
|
||||
group: ["@customer-portal/domain/**/src/**"],
|
||||
message:
|
||||
"Don't import from domain package internals. Use @customer-portal/domain/<domain> or its Providers namespace instead.",
|
||||
},
|
||||
{
|
||||
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/<domain> or define locally.",
|
||||
message: "Import from @customer-portal/domain/<module> instead of internals.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// 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 + prevent domain type duplication
|
||||
// BFF strict type safety
|
||||
{
|
||||
files: ["apps/bff/**/*.ts"],
|
||||
rules: {
|
||||
@ -203,40 +147,14 @@ export default [
|
||||
"@typescript-eslint/no-unsafe-argument": "error",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector:
|
||||
"TSInterfaceDeclaration[id.name=/^(Invoice|InvoiceItem|Subscription|PaymentMethod|SimDetails)$/]",
|
||||
message:
|
||||
"Don't re-declare domain types in application code. Import from @customer-portal/domain/<domain> instead.",
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"TSTypeAliasDeclaration[id.name=/^(Invoice|InvoiceItem|Subscription|PaymentMethod|SimDetails)$/]",
|
||||
message:
|
||||
"Don't re-declare domain types in application code. Import from @customer-portal/domain/<domain> instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// Integration packages: must use domain providers and avoid legacy packages
|
||||
// Node globals for config files
|
||||
{
|
||||
files: ["packages/integrations/**/src/**/*.ts"],
|
||||
rules: {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ["@customer-portal/contracts", "@customer-portal/contracts/*", "@customer-portal/schemas", "@customer-portal/schemas/*"],
|
||||
message:
|
||||
"Legacy contracts/schemas packages have been removed. Import from @customer-portal/domain/<domain>/providers instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
files: ["*.config.mjs", "apps/portal/next.config.mjs"],
|
||||
languageOptions: {
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
39
package.json
39
package.json
@ -9,10 +9,9 @@
|
||||
},
|
||||
"packageManager": "pnpm@10.25.0+sha512.5e82639027af37cf832061bcc6d639c219634488e0f2baebe785028a793de7b525ffcd3f7ff574f5e9860654e098fe852ba8ac5dd5cefe1767d23a020a92f501",
|
||||
"scripts": {
|
||||
"predev": "pnpm --filter @customer-portal/domain build",
|
||||
"dev": "./scripts/dev/manage.sh apps",
|
||||
"dev:all": "pnpm --parallel --filter @customer-portal/domain --filter @customer-portal/portal --filter @customer-portal/bff run dev",
|
||||
"build": "pnpm --recursive run build",
|
||||
"dev:all": "pnpm --filter @customer-portal/domain build && pnpm --parallel --filter @customer-portal/portal --filter @customer-portal/bff run dev",
|
||||
"build": "pnpm --filter @customer-portal/domain build && pnpm --recursive --filter=!@customer-portal/domain run build",
|
||||
"start": "pnpm --parallel --filter @customer-portal/portal --filter @customer-portal/bff run start",
|
||||
"test": "pnpm --recursive run test",
|
||||
"lint": "pnpm --recursive run lint",
|
||||
@ -20,23 +19,14 @@
|
||||
"format": "prettier -w .",
|
||||
"format:check": "prettier -c .",
|
||||
"prepare": "husky",
|
||||
"type-check": "pnpm type-check:packages && pnpm type-check:apps",
|
||||
"type-check:workspace": "tsc -b --noEmit",
|
||||
"type-check:packages": "pnpm --workspace-concurrency=1 --filter @customer-portal/domain --filter @customer-portal/validation --filter @customer-portal/logging run type-check",
|
||||
"type-check:apps": "pnpm --workspace-concurrency=1 --filter @customer-portal/bff --filter @customer-portal/portal run type-check",
|
||||
"type-check": "pnpm --filter @customer-portal/domain run type-check && pnpm --filter @customer-portal/bff --filter @customer-portal/portal run type-check",
|
||||
"clean": "pnpm --recursive run clean",
|
||||
"dev:start": "./scripts/dev/manage.sh start",
|
||||
"dev:stop": "./scripts/dev/manage.sh stop",
|
||||
"dev:restart": "./scripts/dev/manage.sh restart",
|
||||
"analyze": "pnpm --filter @customer-portal/portal run analyze",
|
||||
"bundle-analyze": "./scripts/bundle-analyze.sh",
|
||||
"dev:tools": "./scripts/dev/manage.sh tools",
|
||||
"dev:apps": "./scripts/dev/manage.sh apps",
|
||||
"dev:logs": "./scripts/dev/manage.sh logs",
|
||||
"dev:status": "./scripts/dev/manage.sh status",
|
||||
"dev:migrate": "./scripts/dev/manage.sh migrate",
|
||||
"dev:studio": "./scripts/dev/manage.sh studio",
|
||||
"dev:reset": "./scripts/dev/manage.sh reset",
|
||||
"prod:deploy": "./scripts/prod/manage.sh deploy",
|
||||
"prod:start": "./scripts/prod/manage.sh start",
|
||||
"prod:stop": "./scripts/prod/manage.sh stop",
|
||||
@ -48,38 +38,29 @@
|
||||
"prod:backup": "./scripts/prod/manage.sh backup",
|
||||
"prod:cleanup": "./scripts/prod/manage.sh cleanup",
|
||||
"db:migrate": "pnpm --filter @customer-portal/bff run db:migrate",
|
||||
"db:generate": "pnpm --filter @customer-portal/bff run db:generate",
|
||||
"db:studio": "pnpm --filter @customer-portal/bff run db:studio",
|
||||
"db:reset": "pnpm --filter @customer-portal/bff run db:reset",
|
||||
"update:check": "pnpm outdated --recursive",
|
||||
"update:all": "pnpm update --recursive --latest && pnpm audit && pnpm type-check",
|
||||
"update:safe": "pnpm update --recursive && pnpm audit && pnpm type-check",
|
||||
"security:audit": "pnpm audit",
|
||||
"security:audit-fix": "pnpm audit --fix || pnpm update --recursive && pnpm audit",
|
||||
"security:check": "pnpm audit --audit-level=high",
|
||||
"dev:watch": "pnpm --parallel --filter @customer-portal/domain --filter @customer-portal/portal --filter @customer-portal/bff run dev",
|
||||
"plesk:images": "bash ./scripts/plesk/build-images.sh",
|
||||
"postinstall": "husky install || true"
|
||||
"update:check": "pnpm outdated --recursive",
|
||||
"update:safe": "pnpm update --recursive && pnpm audit && pnpm type-check",
|
||||
"analyze": "pnpm --filter @customer-portal/portal run analyze",
|
||||
"plesk:images": "bash ./scripts/plesk/build-images.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/node": "^24.10.2",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-next": "15.5.0",
|
||||
"eslint-config-next": "16.0.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"globals": "^16.5.0",
|
||||
"husky": "^9.1.7",
|
||||
"pino": "^10.1.0",
|
||||
"prettier": "^3.7.4",
|
||||
"sharp": "^0.34.5",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.49.0",
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ssh2-sftp-client": "^9.0.6",
|
||||
"ssh2-sftp-client": "^12.0.1"
|
||||
"typescript-eslint": "^8.49.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@customer-portal/domain",
|
||||
"version": "1.0.0",
|
||||
"type": "commonjs",
|
||||
"type": "module",
|
||||
"description": "Unified domain layer with contracts, schemas, and provider mappers",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
@ -11,34 +11,118 @@
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./auth": "./dist/auth/index.js",
|
||||
"./auth/*": "./dist/auth/*.js",
|
||||
"./billing": "./dist/billing/index.js",
|
||||
"./billing/*": "./dist/billing/*.js",
|
||||
"./catalog": "./dist/catalog/index.js",
|
||||
"./catalog/*": "./dist/catalog/*.js",
|
||||
"./common": "./dist/common/index.js",
|
||||
"./common/*": "./dist/common/*.js",
|
||||
"./customer": "./dist/customer/index.js",
|
||||
"./customer/*": "./dist/customer/*.js",
|
||||
"./dashboard": "./dist/dashboard/index.js",
|
||||
"./dashboard/*": "./dist/dashboard/*.js",
|
||||
"./mappings": "./dist/mappings/index.js",
|
||||
"./mappings/*": "./dist/mappings/*.js",
|
||||
"./orders": "./dist/orders/index.js",
|
||||
"./orders/*": "./dist/orders/*.js",
|
||||
"./payments": "./dist/payments/index.js",
|
||||
"./payments/*": "./dist/payments/*.js",
|
||||
"./sim": "./dist/sim/index.js",
|
||||
"./sim/*": "./dist/sim/*.js",
|
||||
"./sim/providers/freebit": "./dist/sim/providers/freebit/index.js",
|
||||
"./subscriptions": "./dist/subscriptions/index.js",
|
||||
"./subscriptions/*": "./dist/subscriptions/*.js",
|
||||
"./support": "./dist/support/index.js",
|
||||
"./support/*": "./dist/support/*.js",
|
||||
"./toolkit": "./dist/toolkit/index.js",
|
||||
"./toolkit/*": "./dist/toolkit/*.js"
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./auth": {
|
||||
"import": "./dist/auth/index.js",
|
||||
"types": "./dist/auth/index.d.ts"
|
||||
},
|
||||
"./auth/*": {
|
||||
"import": "./dist/auth/*.js",
|
||||
"types": "./dist/auth/*.d.ts"
|
||||
},
|
||||
"./billing": {
|
||||
"import": "./dist/billing/index.js",
|
||||
"types": "./dist/billing/index.d.ts"
|
||||
},
|
||||
"./billing/*": {
|
||||
"import": "./dist/billing/*.js",
|
||||
"types": "./dist/billing/*.d.ts"
|
||||
},
|
||||
"./catalog": {
|
||||
"import": "./dist/catalog/index.js",
|
||||
"types": "./dist/catalog/index.d.ts"
|
||||
},
|
||||
"./catalog/*": {
|
||||
"import": "./dist/catalog/*.js",
|
||||
"types": "./dist/catalog/*.d.ts"
|
||||
},
|
||||
"./common": {
|
||||
"import": "./dist/common/index.js",
|
||||
"types": "./dist/common/index.d.ts"
|
||||
},
|
||||
"./common/*": {
|
||||
"import": "./dist/common/*.js",
|
||||
"types": "./dist/common/*.d.ts"
|
||||
},
|
||||
"./customer": {
|
||||
"import": "./dist/customer/index.js",
|
||||
"types": "./dist/customer/index.d.ts"
|
||||
},
|
||||
"./customer/*": {
|
||||
"import": "./dist/customer/*.js",
|
||||
"types": "./dist/customer/*.d.ts"
|
||||
},
|
||||
"./dashboard": {
|
||||
"import": "./dist/dashboard/index.js",
|
||||
"types": "./dist/dashboard/index.d.ts"
|
||||
},
|
||||
"./dashboard/*": {
|
||||
"import": "./dist/dashboard/*.js",
|
||||
"types": "./dist/dashboard/*.d.ts"
|
||||
},
|
||||
"./mappings": {
|
||||
"import": "./dist/mappings/index.js",
|
||||
"types": "./dist/mappings/index.d.ts"
|
||||
},
|
||||
"./mappings/*": {
|
||||
"import": "./dist/mappings/*.js",
|
||||
"types": "./dist/mappings/*.d.ts"
|
||||
},
|
||||
"./orders": {
|
||||
"import": "./dist/orders/index.js",
|
||||
"types": "./dist/orders/index.d.ts"
|
||||
},
|
||||
"./orders/*": {
|
||||
"import": "./dist/orders/*.js",
|
||||
"types": "./dist/orders/*.d.ts"
|
||||
},
|
||||
"./payments": {
|
||||
"import": "./dist/payments/index.js",
|
||||
"types": "./dist/payments/index.d.ts"
|
||||
},
|
||||
"./payments/*": {
|
||||
"import": "./dist/payments/*.js",
|
||||
"types": "./dist/payments/*.d.ts"
|
||||
},
|
||||
"./sim": {
|
||||
"import": "./dist/sim/index.js",
|
||||
"types": "./dist/sim/index.d.ts"
|
||||
},
|
||||
"./sim/*": {
|
||||
"import": "./dist/sim/*.js",
|
||||
"types": "./dist/sim/*.d.ts"
|
||||
},
|
||||
"./sim/providers/freebit": {
|
||||
"import": "./dist/sim/providers/freebit/index.js",
|
||||
"types": "./dist/sim/providers/freebit/index.d.ts"
|
||||
},
|
||||
"./subscriptions": {
|
||||
"import": "./dist/subscriptions/index.js",
|
||||
"types": "./dist/subscriptions/index.d.ts"
|
||||
},
|
||||
"./subscriptions/*": {
|
||||
"import": "./dist/subscriptions/*.js",
|
||||
"types": "./dist/subscriptions/*.d.ts"
|
||||
},
|
||||
"./support": {
|
||||
"import": "./dist/support/index.js",
|
||||
"types": "./dist/support/index.d.ts"
|
||||
},
|
||||
"./support/*": {
|
||||
"import": "./dist/support/*.js",
|
||||
"types": "./dist/support/*.d.ts"
|
||||
},
|
||||
"./toolkit": {
|
||||
"import": "./dist/toolkit/index.js",
|
||||
"types": "./dist/toolkit/index.d.ts"
|
||||
},
|
||||
"./toolkit/*": {
|
||||
"import": "./dist/toolkit/*.js",
|
||||
"types": "./dist/toolkit/*.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "pnpm run clean",
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
"composite": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo"
|
||||
"tsBuildInfoFile": "./dist/.tsbuildinfo",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"verbatimModuleSyntax": false
|
||||
},
|
||||
"include": [
|
||||
"auth/**/*",
|
||||
@ -13,15 +16,15 @@
|
||||
"common/**/*",
|
||||
"customer/**/*",
|
||||
"dashboard/**/*",
|
||||
"providers/**/*",
|
||||
"mappings/**/*",
|
||||
"orders/**/*",
|
||||
"payments/**/*",
|
||||
"providers/**/*",
|
||||
"sim/**/*",
|
||||
"subscriptions/**/*",
|
||||
"support/**/*",
|
||||
"toolkit/**/*",
|
||||
"index.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
751
pnpm-lock.yaml
generated
751
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
// Language and Environment (Node.js 22 supports ES2023 features)
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
||||
// Type Checking - Strict Mode
|
||||
"target": "ES2024",
|
||||
"lib": ["ES2024"],
|
||||
"strict": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
|
||||
// Modules
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
// Emit
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
|
||||
// Interop Constraints
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
|
||||
// Completeness
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,6 @@
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./packages/domain" },
|
||||
{ "path": "./packages/logging" },
|
||||
{ "path": "./packages/validation" },
|
||||
{ "path": "./packages/integrations/whmcs" },
|
||||
{ "path": "./packages/integrations/freebit" },
|
||||
{ "path": "./apps/bff" },
|
||||
{ "path": "./apps/portal" }
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user