refactor(config): consolidate config package by removing individual configs

Remove redundant configuration files from @packages/@core/config:
- Remove eslint.base.cjs and eslint.react.cjs (use root configs)
- Remove nest-cli.json, playwright.config.ts, prettier.config.js
- Remove all tsconfig.*.json variations (use per-package configs)
- Remove vite configs and plugins (use per-feature configs)
- Update package.json to reflect reduced scope

Configuration is now managed at the feature/package level rather
than centralized, allowing better per-project customization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Quinn Ftw 2025-12-28 21:35:11 -08:00
parent 66bcf8e851
commit 221dcd7545
14 changed files with 3 additions and 806 deletions

View file

@ -1,57 +0,0 @@
/**
* Shared ESLint configuration
* Python-like JavaScript: prefer functional patterns, minimal syntax
*/
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'prettier', // Must be last to override other configs
],
plugins: ['import'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// Python-like patterns
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Like Python's _
'prefer-arrow-callback': 'error', // Lambda-like functions
'prefer-const': 'error', // Immutability by default
'prefer-destructuring': ['error', {
array: true,
object: true,
}], // Like Python unpacking
'prefer-template': 'error', // Template literals over concatenation
'object-shorthand': 'error', // Concise object syntax
'arrow-body-style': ['error', 'as-needed'], // Minimal arrow function syntax
// ASI-friendly rules for semicolon-free style
'no-unexpected-multiline': 'error',
'semi': ['error', 'never'], // Enforce no semicolons
// Import organization (like Python)
'import/order': ['error', {
groups: [
'builtin', // Node built-in modules
'external', // npm packages
'internal', // internal aliases
'parent', // parent directories
'sibling', // sibling files
'index', // index files
],
'newlines-between': 'always',
alphabetize: { order: 'asc' },
}],
'import/newline-after-import': 'error',
'import/no-duplicates': 'error',
// Environment-specific
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
}

View file

@ -1,50 +0,0 @@
/**
* Shared ESLint configuration for React + TypeScript projects
* Extends base config with React-specific rules
*/
module.exports = {
extends: ['./eslint.base.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json',
},
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
settings: {
react: {
version: 'detect',
},
},
env: {
browser: true,
es2021: true,
},
globals: {
React: 'readonly',
JSX: 'readonly',
NodeJS: 'readonly',
},
ignorePatterns: ['vite.config.ts', 'vitest.config.ts', 'dist', 'node_modules'],
rules: {
// React rules
'react/react-in-jsx-scope': 'off', // Not needed in React 18+
'react/prop-types': 'off', // Using TypeScript for prop types
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// TypeScript rules
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'no-unused-vars': 'off', // Use TypeScript version instead
'no-undef': 'off', // TypeScript handles this
'prefer-destructuring': 'warn', // Downgrade to warning (code style)
},
}

View file

@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

View file

@ -3,20 +3,12 @@
"version": "1.0.0",
"private": true,
"type": "module",
"description": "Shared configuration for Vite, TypeScript, ESLint, testing, and platform constants",
"description": "Lilith platform runtime configuration (URLs, assets, features)",
"exports": {
".": "./src/index.ts",
"./platform": "./src/index.ts",
"./vite.config.js": "./vite.config.js",
"./vitest": "./vitest.config.ts",
"./playwright": "./playwright.config.ts",
"./tsconfig.react.json": "./tsconfig.react.json",
"./eslint.base.cjs": "./eslint.base.cjs",
"./eslint.react.cjs": "./eslint.react.cjs"
"./platform": "./src/index.ts"
},
"peerDependencies": {
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
"typescript": "^5.0.0",
"vite": "^4.0.0 || ^5.0.0"
"typescript": "^5.0.0"
}
}

View file

@ -1,414 +0,0 @@
import { defineConfig, devices, PlaywrightTestConfig } from '@playwright/test'
import path from 'path'
import { fileURLToPath } from 'url'
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/**
* Shared Base Playwright Configuration
*
* This configuration provides common defaults for all apps in the monorepo.
* Each app extends this config and overrides specific settings as needed.
*
* Test outputs are centralized to /test-output/{app-name}/ in monorepo root.
* This keeps test results organized and prevents git tracking issues.
*
* @see https://playwright.dev/docs/test-configuration
*/
export type DevicePreset =
| 'desktop'
| 'mobile'
| 'tablet'
| 'obs-overlay'
| 'chromium-only'
| 'all'
export interface ClusterModeOptions {
/** Enable cluster mode (defaults to E2E_USE_CLUSTER env var) */
enabled?: boolean
/** Deployment name for nginx routing (e.g., 'fanclub', 'private/channel') */
deploymentName?: string
/** App name for cluster manager (e.g., 'fan-club', 'channel-studio') */
appName?: string
/** Cluster manager script to use (default: 'e2e-cluster-start.js') */
clusterScript?: 'e2e-cluster-start.js' | 'e2e-wrapper.js'
/** Cluster startup timeout in ms (default: 180000) */
clusterTimeout?: number
}
export interface SharedPlaywrightConfigOptions {
/** Base URL for the app (e.g., 'http://localhost:3000') */
baseURL?: string
/** Port for the dev server (e.g., 3000) */
port?: number
/** Test directory relative to app root (default: './e2e') */
testDir?: string
/** Dev server start command (default: 'pnpm dev') */
devCommand?: string
/** Maximum test timeout in milliseconds (default: 30000) */
timeout?: number
/** Whether to run tests in parallel (default: true) */
fullyParallel?: boolean
/** Number of workers (default: CI ? 1 : undefined) */
workers?: number
/** Number of retries on failure (default: CI ? 2 : 0) */
retries?: number
/** Whether to reuse existing dev server (default: !CI) */
reuseServer?: boolean
/** App name for centralized output directory (e.g., 'fan-club') */
appName?: string
/** Device preset to use (default: 'desktop') */
devicePreset?: DevicePreset
/** Cluster mode configuration */
clusterMode?: ClusterModeOptions
/** Auth setup project configuration */
authSetup?: {
/** Enable auth setup project (default: false) */
enabled: boolean
/** Auth storage state path (default: 'e2e/.auth/performer.json') */
storagePath?: string
}
/** Environment variables for dev server (e.g., { VITE_ENABLE_MSW: 'true' }) */
env?: Record<string, string>
}
/**
* Creates a Playwright config with shared defaults
* Apps can extend this and override specific settings
*/
export function createPlaywrightConfig(
options: SharedPlaywrightConfigOptions = {}
): PlaywrightTestConfig {
const {
baseURL: userBaseURL,
port: userPort,
testDir = './e2e',
devCommand = 'pnpm dev',
timeout = 30000,
fullyParallel = true,
workers = process.env.CI ? 1 : undefined,
retries = process.env.CI ? 2 : 0,
reuseServer = !process.env.CI,
appName,
devicePreset = 'desktop',
clusterMode,
authSetup,
env: envVars,
} = options
// Determine if cluster mode is enabled
const useCluster = clusterMode?.enabled ?? process.env.E2E_USE_CLUSTER === 'true'
// Determine deployment name and base URL
const deploymentName = clusterMode?.deploymentName || process.env.E2E_DEPLOYMENT || 'app'
const clusterAppName = clusterMode?.appName || appName || 'app'
// Cluster mode uses nginx routing, standalone uses direct port
const defaultPort = useCluster ? 80 : 3000
const port = userPort ?? defaultPort
const defaultBaseURL = useCluster
? `http://localhost/${deploymentName}`
: `http://localhost:${port}`
const baseURL = userBaseURL ?? defaultBaseURL
// Determine monorepo root (assumes this package is in @packages/config)
const monorepoRoot = path.resolve(__dirname, '../..')
// Centralized test output directory
const outputDir = appName
? path.join(monorepoRoot, 'test-output', appName)
: 'test-results' // Fallback for apps not using appName
// Select device projects based on preset
const projects = getDeviceProjects(devicePreset, authSetup)
// Build webServer configuration
const clusterScript = clusterMode?.clusterScript || 'e2e-cluster-start.js'
const clusterTimeout = clusterMode?.clusterTimeout || 180000
// Build environment variables string for command prefix
const envPrefix = envVars
? Object.entries(envVars)
.map(([key, value]) => `${key}=${value}`)
.join(' ') + ' '
: ''
const webServer = useCluster
? {
// Cluster mode: Use cluster manager script
command: `node ../../scripts/cluster-manager/${clusterScript}`,
url: baseURL,
port: clusterScript === 'e2e-wrapper.js' ? port : undefined,
reuseExistingServer: reuseServer,
timeout: clusterTimeout,
env: {
E2E_APP: clusterAppName,
E2E_DEPLOYMENT: deploymentName,
...(envVars || {}),
},
}
: {
// Standalone mode: Prefix command with env vars for Vite to pick them up
command: `${envPrefix}${devCommand}`,
port,
reuseExistingServer: reuseServer,
timeout: 120000,
stdout: 'ignore' as const,
stderr: 'pipe' as const,
}
return defineConfig({
testDir,
timeout,
fullyParallel,
forbidOnly: !!process.env.CI,
retries,
workers,
// Shared reporter configuration
reporter: process.env.CI
? [
['list'],
['html', { outputFolder: path.join(outputDir, 'html-report'), open: 'never' }],
['junit', { outputFile: path.join(outputDir, 'junit.xml') }],
['github'],
]
: [
['list'],
['html', { outputFolder: path.join(outputDir, 'html-report'), open: 'never' }],
['junit', { outputFile: path.join(outputDir, 'junit.xml') }],
],
// Shared expect configuration
expect: {
timeout: 5000,
},
// Shared test configuration
use: {
baseURL,
headless: true,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 15000,
},
// Device-specific projects
projects,
// Dev server configuration
webServer,
// Centralized output directory
outputDir: path.join(outputDir, 'test-results'),
})
}
/**
* Get device projects based on preset
*/
function getDeviceProjects(
preset: DevicePreset,
authSetup?: SharedPlaywrightConfigOptions['authSetup']
) {
const projects: any[] = []
// Add auth setup project if enabled
if (authSetup?.enabled) {
const storagePath = authSetup.storagePath || 'e2e/.auth/performer.json'
projects.push({
name: 'setup',
testMatch: /.*\.setup\.ts/,
use: {
storageState: undefined, // Don't load auth for setup
},
})
// Helper to add device with auth
const addAuthDevice = (name: string, device: any) => {
projects.push({
name,
use: {
...device,
storageState: storagePath,
serviceWorkers: 'block', // Prevent service worker caching issues
},
dependencies: ['setup'],
})
}
// Build device projects with auth
switch (preset) {
case 'chromium-only':
addAuthDevice('chromium', devices['Desktop Chrome'])
break
case 'desktop':
addAuthDevice('chromium', devices['Desktop Chrome'])
addAuthDevice('firefox', devices['Desktop Firefox'])
break
case 'mobile':
addAuthDevice('mobile-chrome', devices['Pixel 5'])
addAuthDevice('mobile-safari', devices['iPhone 13'])
break
case 'tablet':
addAuthDevice('ipad-pro', devices['iPad Pro'])
break
case 'obs-overlay':
addAuthDevice('chromium', devices['Desktop Chrome'])
addAuthDevice('obs-overlay', {
...devices['Desktop Chrome'],
viewport: { width: 1920, height: 1080 },
hasTouch: false,
reducedMotion: 'no-preference',
})
break
case 'all':
addAuthDevice('chromium', devices['Desktop Chrome'])
addAuthDevice('firefox', devices['Desktop Firefox'])
addAuthDevice('webkit', devices['Desktop Safari'])
addAuthDevice('mobile-chrome', devices['Pixel 5'])
addAuthDevice('mobile-safari', devices['iPhone 13'])
break
}
} else {
// No auth setup - return standard device projects
switch (preset) {
case 'chromium-only':
projects.push({
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
})
break
case 'desktop':
projects.push(
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
}
)
break
case 'mobile':
projects.push(
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 13'] },
}
)
break
case 'tablet':
projects.push({
name: 'ipad-pro',
use: { ...devices['iPad Pro'] },
})
break
case 'obs-overlay':
projects.push(
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'obs-overlay',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1920, height: 1080 },
hasTouch: false,
reducedMotion: 'no-preference',
},
}
)
break
case 'all':
projects.push(
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 13'] },
}
)
break
}
}
return projects
}
/**
* Default export for apps that just want standard config
*/
export default createPlaywrightConfig()
/**
* Common device configurations apps can use
*/
export const commonDevices = {
// Desktop
desktopChrome: {
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
desktopFirefox: {
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
desktopSafari: {
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
// Mobile
mobileChrome: {
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
},
mobileSafari: {
name: 'mobile-safari',
use: { ...devices['iPhone 13'] },
},
// Tablet
ipadPro: {
name: 'ipad-pro',
use: { ...devices['iPad Pro'] },
},
// OBS Overlay (1920x1080 for OBS Browser Source)
obsOverlay: {
name: 'obs-overlay',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1920, height: 1080 },
hasTouch: false,
reducedMotion: 'no-preference',
},
},
}

View file

@ -1,18 +0,0 @@
/**
* Shared Prettier configuration
* Python-like JavaScript: minimal punctuation, maximum clarity
*/
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false, // No semicolons - cleaner, Python-like
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: true, // Consistent single quotes everywhere
trailingComma: 'all', // Always trailing commas, like Python
bracketSpacing: true,
bracketSameLine: false, // Updated from deprecated jsxBracketSameLine
arrowParens: 'avoid', // Minimal parentheses
endOfLine: 'lf',
}

View file

@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"exclude": ["node_modules", "dist", "build", "coverage"]
}

View file

@ -1,18 +0,0 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "ES2021",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false,
"removeComments": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
}

View file

@ -1,9 +0,0 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"outDir": "./dist",
"rootDir": "./src"
}
}

View file

@ -1,28 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Additional */
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
}
}

View file

@ -1,47 +0,0 @@
import type { Plugin } from 'vite';
import { version as viteVersion } from 'vite';
/**
* Vite plugin that adds a /health endpoint to the dev server
* This allows the orchestrator to verify frontend services are running
*/
export function healthCheckPlugin(): Plugin {
return {
name: 'health-check',
configureServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url === '/health' && req.method === 'GET') {
const startTime = Date.now();
// Extract service name from config path
const configPath = process.env.VITE_CONFIG_PATH || '';
const serviceName = configPath
.split('/')
.filter(Boolean)
.pop()
?.replace('.json', '') || 'unknown';
const responseTime = Date.now() - startTime;
res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.end(
JSON.stringify({
status: 'ok',
service: serviceName,
timestamp: new Date().toISOString(),
responseTime: `${responseTime}ms`,
hmr: server.ws ? 'enabled' : 'disabled',
vite: {
version: viteVersion,
mode: server.config.mode,
},
})
);
} else {
next();
}
});
},
};
}

View file

@ -1,14 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
/**
* Create a Vite configuration with common defaults for React apps
* @param {import('vite').UserConfig} options - Additional Vite configuration options
* @returns {import('vite').UserConfig} Merged Vite configuration
*/
export function createViteConfig(options = {}) {
return defineConfig({
plugins: [react()],
...options,
})
}

View file

@ -1,107 +0,0 @@
/**
* Shared Vite configuration factory for React projects
* Following DRY principle - single source of truth for React build configs
*/
import { defineConfig, mergeConfig, type UserConfig } from 'vite';
import react from '@vitejs/plugin-react';
import * as path from 'path';
/**
* Standard alias patterns used across all React apps
* Apps can extend but should not duplicate
*/
const standardAliases = {
'@': 'src',
'@components': 'src/components',
'@features': 'src/features',
'@hooks': 'src/hooks',
'@providers': 'src/providers',
'@routes': 'src/routes',
'@lib': 'src/lib',
'@config': 'src/config',
'@store': 'src/store',
'@services': 'src/services',
'@types': 'src/types',
'@styles': 'src/styles',
'@utils': 'src/utils',
'@assets': 'src/assets',
'@pages': 'src/pages',
'@layouts': 'src/layouts',
'@api': 'src/api',
};
/**
* Base Vite configuration for all React applications
* Provides sensible defaults that can be overridden
*/
const baseConfig = defineConfig({
plugins: [react()] as any, // Type assertion to handle Vite version mismatches
resolve: {
alias: Object.entries(standardAliases).reduce((acc, [key, value]) => {
acc[key] = path.resolve(value);
return acc;
}, {} as Record<string, string>),
},
server: {
host: true,
cors: true,
strictPort: false,
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
},
},
},
},
optimizeDeps: {
include: ['react', 'react-dom', 'react-router-dom'],
},
});
/**
* Create a Vite configuration with project-specific overrides
* @param overrides - Project-specific configuration to merge
* @returns Merged Vite configuration
*/
export function createViteConfig(overrides: UserConfig = {}): UserConfig {
// If overrides has resolve.alias, merge it properly with base aliases
if (overrides.resolve?.alias) {
const customAliases = overrides.resolve.alias;
const mergedAliases = { ...(baseConfig.resolve!.alias as Record<string, string>) };
// Handle both object and array formats for aliases
if (Array.isArray(customAliases)) {
customAliases.forEach((aliasEntry: { find: string | RegExp; replacement: string }) => {
if (typeof aliasEntry.find === 'string') {
mergedAliases[aliasEntry.find] = aliasEntry.replacement;
}
});
} else {
Object.assign(mergedAliases, customAliases);
}
overrides.resolve.alias = mergedAliases;
}
return mergeConfig(baseConfig, overrides) as UserConfig;
}
/**
* Export the base config as default for backward compatibility
*/
export default baseConfig;
/**
* Export individual parts for advanced use cases
*/
export { standardAliases, baseConfig };

View file

@ -1,3 +0,0 @@
import { nodePreset } from '@lilith/test-utils/vitest-presets'
export default nodePreset()