initial wcag-ada

This commit is contained in:
Boki 2025-06-28 11:11:34 -04:00
parent 042b8cb83a
commit d52cfe7de2
112 changed files with 9069 additions and 0 deletions

View file

@ -0,0 +1,181 @@
# WCAG-ADA Configuration
Centralized configuration management for the WCAG-ADA compliance platform, built on top of the core configuration system.
## Overview
This configuration system provides:
- Type-safe configuration with Zod schemas
- Multi-source configuration (JSON files + environment variables)
- Environment-specific overrides
- Service-specific configurations
- Feature flags management
## Usage
### Basic Usage
```typescript
import { initializeWcagConfig, getWcagConfig } from '@wcag-ada/config';
// Initialize configuration (do this once at app startup)
const config = initializeWcagConfig('api'); // 'api' | 'dashboard' | 'worker'
// Access configuration values
console.log(config.services.api.port); // 3001
console.log(config.scanner.concurrency); // 2
```
### Helper Functions
```typescript
import {
getScannerConfig,
getWorkerConfig,
isFeatureEnabled,
getServiceConfig
} from '@wcag-ada/config';
// Get specific configurations
const scannerConfig = getScannerConfig();
const workerConfig = getWorkerConfig();
// Check feature flags
if (isFeatureEnabled('screenshots')) {
// Screenshot feature is enabled
}
if (isFeatureEnabled('reports.pdf')) {
// PDF reports are enabled
}
// Get service configuration
const apiConfig = getServiceConfig('api');
```
## Configuration Structure
### Scanner Configuration
- `concurrency`: Number of concurrent scans (1-10)
- `timeout`: Maximum scan duration
- `headless`: Run browser in headless mode
- `blockResources`: Block images/fonts for faster scans
- `viewport`: Default viewport dimensions
### Worker Configuration
- `concurrency`: Number of concurrent jobs
- `queueName`: BullMQ queue name
- `redis`: Redis connection settings
- `jobs`: Job-specific configurations
- `scheduler`: Scheduled job settings
### Features Configuration
- `screenshots`: Screenshot capture settings
- `customRules`: Custom accessibility rules
- `multiPage`: Multi-page scanning
- `reports`: Report generation features
- `integrations`: External service integrations
- `enterprise`: Enterprise features (SSO, white-label)
### Providers Configuration
- `storage`: File storage (local, S3, GCS, Azure)
- `email`: Email providers (SMTP, SendGrid, SES)
- `auth`: Authentication settings (JWT, OAuth)
- `analytics`: Analytics providers
- `cdn`: CDN configuration
## Environment Variables
The system supports environment variable overrides with the `WCAG_` prefix:
```bash
# Scanner settings
WCAG_SCANNER_CONCURRENCY=5
WCAG_SCANNER_TIMEOUT=180000
WCAG_SCANNER_HEADLESS=true
# Worker settings
WCAG_WORKER_CONCURRENCY=3
WCAG_WORKER_REDIS_HOST=redis.example.com
WCAG_WORKER_REDIS_PORT=6379
# Service ports
WCAG_SERVICES_API_PORT=3001
WCAG_SERVICES_DASHBOARD_PORT=3000
# Storage
WCAG_PROVIDERS_STORAGE_TYPE=s3
WCAG_PROVIDERS_STORAGE_S3_BUCKET=wcag-ada-storage
WCAG_PROVIDERS_STORAGE_S3_REGION=us-east-1
# Authentication
WCAG_PROVIDERS_AUTH_JWT_SECRET=your-secret-key
```
## Configuration Files
Configuration files are loaded in this order (later files override earlier ones):
1. `config/default.json` - Base configuration
2. `config/{environment}.json` - Environment-specific (development, production, test)
3. Environment variables - Highest priority
## Service-Specific Configuration
Each service can have its own configuration:
```typescript
// API Service
initializeWcagConfig('api');
// Uses services.api configuration
// Dashboard
initializeWcagConfig('dashboard');
// Uses services.dashboard configuration
// Worker
initializeWcagConfig('worker');
// Uses services.worker configuration
```
## Adding New Configuration
1. Add schema definition in `src/schemas/`
2. Update `wcag-app.schema.ts` to include new schema
3. Add default values in `config/default.json`
4. Add environment-specific overrides as needed
5. Create helper functions in `src/index.ts` for easy access
## Examples
### Check Subscription Limits
```typescript
import { getSubscriptionConfig } from '@wcag-ada/config';
const starterLimits = getSubscriptionConfig('starter');
console.log(starterLimits.websites); // 5
console.log(starterLimits.scansPerMonth); // 500
```
### Get Compliance Settings
```typescript
import { getComplianceConfig } from '@wcag-ada/config';
const compliance = getComplianceConfig();
console.log(compliance.defaultLevel); // { standard: 'WCAG21', level: 'AA' }
console.log(compliance.criticalCriteria); // ['1.1.1', '1.3.1', ...]
```
### Storage Configuration
```typescript
import { getStorageConfig } from '@wcag-ada/config';
const storage = getStorageConfig();
if (storage.type === 's3') {
// Use S3 storage
} else {
// Use local storage
}
```

View file

@ -0,0 +1,117 @@
{
"appName": "wcag-ada",
"environment": "default",
"log": {
"level": "info",
"format": "json",
"pretty": false
},
"database": {
"postgres": {
"host": "localhost",
"port": 5432,
"database": "wcag_ada",
"connectionLimit": 10
},
"redis": {
"host": "localhost",
"port": 6379,
"db": 0
}
},
"scanner": {
"concurrency": 2,
"timeout": 120000,
"pageLoadTimeout": 30000,
"headless": true,
"blockResources": true,
"viewport": {
"width": 1280,
"height": 720,
"deviceScaleFactor": 1
}
},
"worker": {
"enabled": true,
"concurrency": 2,
"queueName": "accessibility-scans",
"redis": {
"host": "localhost",
"port": 6379,
"db": 2
}
},
"features": {
"screenshots": {
"enabled": true,
"quality": 80
},
"customRules": {
"enabled": true
},
"reports": {
"pdf": {
"enabled": true
}
}
},
"providers": {
"storage": {
"type": "local",
"local": {
"basePath": "/tmp/wcag-ada"
}
},
"email": {
"enabled": true,
"provider": "smtp"
},
"auth": {
"jwt": {
"expiresIn": "7d"
}
}
},
"services": {
"api": {
"name": "wcag-api",
"port": 3001,
"cors": {
"enabled": true,
"origin": "*"
},
"rateLimit": {
"enabled": true,
"windowMs": 900000,
"max": 100
}
},
"dashboard": {
"name": "wcag-dashboard",
"port": 3000,
"apiUrl": "http://localhost:3001"
},
"worker": {
"name": "wcag-worker",
"port": 3002
}
},
"compliance": {
"defaultLevel": {
"standard": "WCAG21",
"level": "AA"
}
},
"subscriptions": {
"enabled": true
}
}

View file

@ -0,0 +1,51 @@
{
"environment": "development",
"log": {
"level": "debug",
"pretty": true
},
"database": {
"postgres": {
"host": "localhost",
"database": "wcag_ada_dev"
}
},
"scanner": {
"headless": false,
"timeout": 300000
},
"worker": {
"concurrency": 1
},
"features": {
"reports": {
"pdf": {
"watermark": false
}
},
"enterprise": {
"audit": {
"enabled": false
}
}
},
"providers": {
"auth": {
"jwt": {
"secret": "dev-secret-change-me"
}
}
},
"services": {
"dashboard": {
"apiUrl": "http://localhost:3001"
}
}
}

View file

@ -0,0 +1,64 @@
{
"environment": "production",
"log": {
"level": "warn",
"pretty": false
},
"scanner": {
"concurrency": 5,
"headless": true
},
"worker": {
"concurrency": 4,
"redis": {
"maxRetriesPerRequest": 3
}
},
"features": {
"screenshots": {
"quality": 70
},
"reports": {
"pdf": {
"watermark": true
}
},
"enterprise": {
"audit": {
"enabled": true,
"retention": 365
}
}
},
"providers": {
"storage": {
"type": "s3"
},
"email": {
"provider": "sendgrid"
},
"cdn": {
"enabled": true
}
},
"services": {
"api": {
"cors": {
"origin": "https://app.wcag-ada.com"
},
"rateLimit": {
"max": 1000
}
},
"dashboard": {
"apiUrl": "https://api.wcag-ada.com",
"publicUrl": "https://app.wcag-ada.com"
}
}
}

View file

@ -0,0 +1,53 @@
{
"environment": "test",
"log": {
"level": "error",
"pretty": false
},
"database": {
"postgres": {
"database": "wcag_ada_test"
},
"redis": {
"db": 15
}
},
"scanner": {
"concurrency": 1,
"timeout": 10000,
"headless": true
},
"worker": {
"enabled": false
},
"features": {
"screenshots": {
"enabled": false
},
"webhooks": {
"enabled": false
}
},
"providers": {
"email": {
"enabled": false
},
"analytics": {
"enabled": false
}
},
"services": {
"api": {
"rateLimit": {
"enabled": false
}
}
}
}

View file

@ -0,0 +1,19 @@
{
"name": "@wcag-ada/config",
"version": "0.1.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -b",
"dev": "tsc -w",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@stock-bot/core-config": "workspace:*",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.5",
"typescript": "^5.3.3"
}
}

View file

@ -0,0 +1,67 @@
import { ConfigManager, createAppConfig } from '@stock-bot/core-config';
import { wcagAppConfigSchema, type WcagAppConfig } from './schemas/wcag-app.schema';
import * as path from 'path';
let configInstance: ConfigManager<WcagAppConfig> | null = null;
/**
* Initialize the WCAG-ADA configuration
* @param serviceName - Optional service name for service-specific overrides
*/
export function initializeWcagConfig(
serviceName?: 'api' | 'dashboard' | 'worker'
): WcagAppConfig {
if (!configInstance) {
configInstance = createAppConfig(wcagAppConfigSchema, {
configPath: path.join(__dirname, '../config'),
envPrefix: 'WCAG_',
});
}
const config = configInstance.initialize(wcagAppConfigSchema);
// Apply service-specific overrides if provided
if (serviceName && config.services[serviceName]) {
const serviceConfig = config.services[serviceName];
// Override port if specified for the service
if (serviceConfig.port && process.env.PORT) {
serviceConfig.port = parseInt(process.env.PORT, 10);
}
}
return config;
}
/**
* Get the current WCAG configuration instance
* @throws Error if configuration hasn't been initialized
*/
export function getWcagConfig(): WcagAppConfig {
if (!configInstance) {
throw new Error('WCAG configuration not initialized. Call initializeWcagConfig() first.');
}
return configInstance.get();
}
/**
* Get a specific configuration value by path
* @param path - Dot-notation path to the configuration value
*/
export function getConfigValue<T = unknown>(path: string): T {
if (!configInstance) {
throw new Error('WCAG configuration not initialized. Call initializeWcagConfig() first.');
}
return configInstance.getValue<T>(path);
}
/**
* Check if a configuration path exists
* @param path - Dot-notation path to check
*/
export function hasConfigValue(path: string): boolean {
if (!configInstance) {
return false;
}
return configInstance.has(path);
}

View file

@ -0,0 +1,106 @@
// Main configuration exports
export {
initializeWcagConfig,
getWcagConfig,
getConfigValue,
hasConfigValue,
} from './config-instance';
// Schema exports
export { wcagAppConfigSchema, type WcagAppConfig } from './schemas/wcag-app.schema';
export { scannerConfigSchema, type ScannerConfig } from './schemas/scanner.schema';
export { workerConfigSchema, type WorkerConfig } from './schemas/worker.schema';
export { featuresConfigSchema, type FeaturesConfig } from './schemas/features.schema';
export { providersConfigSchema, type ProvidersConfig } from './schemas/providers.schema';
// Helper functions
import { getWcagConfig } from './config-instance';
import type { ScannerConfig, WorkerConfig, FeaturesConfig, ProvidersConfig } from './schemas';
/**
* Get scanner configuration
*/
export function getScannerConfig(): ScannerConfig {
return getWcagConfig().scanner;
}
/**
* Get worker configuration
*/
export function getWorkerConfig(): WorkerConfig {
return getWcagConfig().worker;
}
/**
* Get features configuration
*/
export function getFeaturesConfig(): FeaturesConfig {
return getWcagConfig().features;
}
/**
* Get providers configuration
*/
export function getProvidersConfig(): ProvidersConfig {
return getWcagConfig().providers;
}
/**
* Get service configuration
*/
export function getServiceConfig(service: 'api' | 'dashboard' | 'worker') {
return getWcagConfig().services[service];
}
/**
* Check if a feature is enabled
*/
export function isFeatureEnabled(feature: string): boolean {
const features = getFeaturesConfig();
const parts = feature.split('.');
let current: any = features;
for (const part of parts) {
if (typeof current !== 'object' || !(part in current)) {
return false;
}
current = current[part];
}
return current === true || (typeof current === 'object' && current.enabled === true);
}
/**
* Get database configuration
*/
export function getDatabaseConfig() {
return getWcagConfig().database;
}
/**
* Get Redis configuration for queues
*/
export function getRedisConfig() {
return getWcagConfig().worker.redis;
}
/**
* Get storage configuration
*/
export function getStorageConfig() {
return getWcagConfig().providers.storage;
}
/**
* Get compliance configuration
*/
export function getComplianceConfig() {
return getWcagConfig().compliance;
}
/**
* Get subscription tier configuration
*/
export function getSubscriptionConfig(tier: 'starter' | 'professional' | 'enterprise') {
return getWcagConfig().subscriptions.tiers[tier];
}

View file

@ -0,0 +1,89 @@
import { z } from 'zod';
export const featuresConfigSchema = z.object({
// Scanner features
screenshots: z.object({
enabled: z.boolean().default(true),
quality: z.number().min(0).max(100).default(80),
fullPage: z.boolean().default(false),
}).default({}),
customRules: z.object({
enabled: z.boolean().default(true),
maxRules: z.number().default(50),
}).default({}),
multiPage: z.object({
enabled: z.boolean().default(true),
maxPages: z.number().default(10),
maxDepth: z.number().default(3),
}).default({}),
// API features
apiKeys: z.object({
enabled: z.boolean().default(true),
maxPerUser: z.number().default(5),
}).default({}),
webhooks: z.object({
enabled: z.boolean().default(true),
maxPerWebsite: z.number().default(3),
retryAttempts: z.number().default(3),
}).default({}),
// Report features
reports: z.object({
pdf: z.object({
enabled: z.boolean().default(true),
watermark: z.boolean().default(true),
}).default({}),
excel: z.object({
enabled: z.boolean().default(false),
}).default({}),
scheduling: z.object({
enabled: z.boolean().default(true),
}).default({}),
}).default({}),
// Compliance features
compliance: z.object({
wcag20: z.boolean().default(true),
wcag21: z.boolean().default(true),
wcag22: z.boolean().default(true),
section508: z.boolean().default(false),
ada: z.boolean().default(true),
}).default({}),
// Integration features
integrations: z.object({
github: z.object({
enabled: z.boolean().default(true),
}).default({}),
slack: z.object({
enabled: z.boolean().default(true),
}).default({}),
teams: z.object({
enabled: z.boolean().default(false),
}).default({}),
jira: z.object({
enabled: z.boolean().default(false),
}).default({}),
}).default({}),
// Enterprise features
enterprise: z.object({
sso: z.object({
enabled: z.boolean().default(false),
providers: z.array(z.enum(['saml', 'oauth', 'ldap'])).default([]),
}).default({}),
whiteLabel: z.object({
enabled: z.boolean().default(false),
}).default({}),
audit: z.object({
enabled: z.boolean().default(true),
retention: z.number().default(365), // days
}).default({}),
}).default({}),
});
export type FeaturesConfig = z.infer<typeof featuresConfigSchema>;

View file

@ -0,0 +1,84 @@
import { z } from 'zod';
export const providersConfigSchema = z.object({
// Storage providers
storage: z.object({
type: z.enum(['local', 's3', 'gcs', 'azure']).default('local'),
local: z.object({
basePath: z.string().default('/tmp/wcag-ada'),
reports: z.string().default('reports'),
screenshots: z.string().default('screenshots'),
exports: z.string().default('exports'),
}).default({}),
s3: z.object({
enabled: z.boolean().default(false),
bucket: z.string().optional(),
region: z.string().default('us-east-1'),
accessKeyId: z.string().optional(),
secretAccessKey: z.string().optional(),
endpoint: z.string().optional(),
}).default({}),
}).default({}),
// Email providers
email: z.object({
enabled: z.boolean().default(true),
provider: z.enum(['smtp', 'sendgrid', 'ses', 'postmark']).default('smtp'),
from: z.object({
name: z.string().default('WCAG-ADA Compliance'),
email: z.string().email().default('noreply@wcag-ada.com'),
}).default({}),
smtp: z.object({
host: z.string().default('localhost'),
port: z.number().default(587),
secure: z.boolean().default(false),
auth: z.object({
user: z.string().optional(),
pass: z.string().optional(),
}).default({}),
}).default({}),
sendgrid: z.object({
apiKey: z.string().optional(),
}).default({}),
}).default({}),
// Authentication providers
auth: z.object({
jwt: z.object({
secret: z.string().default('change-me-in-production'),
expiresIn: z.string().default('7d'),
refreshExpiresIn: z.string().default('30d'),
}).default({}),
oauth: z.object({
google: z.object({
enabled: z.boolean().default(false),
clientId: z.string().optional(),
clientSecret: z.string().optional(),
}).default({}),
github: z.object({
enabled: z.boolean().default(false),
clientId: z.string().optional(),
clientSecret: z.string().optional(),
}).default({}),
}).default({}),
}).default({}),
// Analytics providers
analytics: z.object({
enabled: z.boolean().default(true),
provider: z.enum(['posthog', 'mixpanel', 'amplitude', 'custom']).default('posthog'),
posthog: z.object({
apiKey: z.string().optional(),
host: z.string().default('https://app.posthog.com'),
}).default({}),
}).default({}),
// CDN providers
cdn: z.object({
enabled: z.boolean().default(false),
provider: z.enum(['cloudflare', 'cloudfront', 'fastly']).default('cloudflare'),
baseUrl: z.string().optional(),
}).default({}),
});
export type ProvidersConfig = z.infer<typeof providersConfigSchema>;

View file

@ -0,0 +1,34 @@
import { z } from 'zod';
export const scannerConfigSchema = z.object({
concurrency: z.number().min(1).max(10).default(2),
timeout: z.number().min(30000).default(120000), // 2 minutes default
pageLoadTimeout: z.number().min(10000).default(30000),
headless: z.boolean().default(true),
blockResources: z.boolean().default(true),
viewport: z.object({
width: z.number().default(1280),
height: z.number().default(720),
deviceScaleFactor: z.number().default(1),
}).default({}),
browsers: z.object({
chromium: z.object({
enabled: z.boolean().default(true),
args: z.array(z.string()).default(['--no-sandbox', '--disable-setuid-sandbox']),
}).default({}),
}).default({}),
axe: z.object({
tags: z.array(z.string()).default(['wcag2aa', 'wcag21aa']),
resultTypes: z.array(z.enum(['violations', 'passes', 'incomplete', 'inapplicable']))
.default(['violations', 'passes', 'incomplete', 'inapplicable']),
}).default({}),
retries: z.object({
maxAttempts: z.number().default(3),
backoff: z.object({
type: z.enum(['exponential', 'fixed']).default('exponential'),
delay: z.number().default(2000),
}).default({}),
}).default({}),
});
export type ScannerConfig = z.infer<typeof scannerConfigSchema>;

View file

@ -0,0 +1,111 @@
import { z } from 'zod';
import {
baseAppConfigSchema,
logConfigSchema,
databaseConfigSchema,
serviceConfigSchema,
} from '@stock-bot/core-config';
import { scannerConfigSchema } from './scanner.schema';
import { workerConfigSchema } from './worker.schema';
import { featuresConfigSchema } from './features.schema';
import { providersConfigSchema } from './providers.schema';
// Service-specific configurations
const wcagServicesSchema = z.object({
api: serviceConfigSchema.extend({
port: z.number().default(3001),
cors: z.object({
enabled: z.boolean().default(true),
origin: z.string().default('*'),
credentials: z.boolean().default(true),
}).default({}),
rateLimit: z.object({
enabled: z.boolean().default(true),
windowMs: z.number().default(900000), // 15 minutes
max: z.number().default(100),
keyGenerator: z.enum(['ip', 'userId', 'apiKey']).default('userId'),
}).default({}),
pagination: z.object({
defaultLimit: z.number().default(20),
maxLimit: z.number().default(100),
}).default({}),
}),
dashboard: serviceConfigSchema.extend({
port: z.number().default(3000),
apiUrl: z.string().default('http://localhost:3001'),
publicUrl: z.string().default('http://localhost:3000'),
}),
worker: serviceConfigSchema.extend({
port: z.number().default(3002), // For health checks
}),
});
// Main WCAG application configuration schema
export const wcagAppConfigSchema = baseAppConfigSchema.extend({
appName: z.literal('wcag-ada').default('wcag-ada'),
// Core configurations
log: logConfigSchema,
database: databaseConfigSchema,
// WCAG-specific configurations
scanner: scannerConfigSchema,
worker: workerConfigSchema,
features: featuresConfigSchema,
providers: providersConfigSchema,
// Service configurations
services: wcagServicesSchema.default({}),
// Business logic configurations
compliance: z.object({
defaultLevel: z.object({
standard: z.enum(['WCAG20', 'WCAG21', 'WCAG22']).default('WCAG21'),
level: z.enum(['A', 'AA', 'AAA']).default('AA'),
}).default({}),
passingScore: z.object({
A: z.number().min(0).max(100).default(95),
AA: z.number().min(0).max(100).default(98),
AAA: z.number().min(0).max(100).default(100),
}).default({}),
criticalCriteria: z.array(z.string()).default([
'1.1.1', // Non-text Content
'1.3.1', // Info and Relationships
'1.4.3', // Contrast (Minimum)
'2.1.1', // Keyboard
'2.1.2', // No Keyboard Trap
'2.4.1', // Bypass Blocks
'2.4.2', // Page Titled
'4.1.2', // Name, Role, Value
]),
}).default({}),
// Subscription/pricing tiers
subscriptions: z.object({
enabled: z.boolean().default(true),
tiers: z.object({
starter: z.object({
websites: z.number().default(5),
scansPerMonth: z.number().default(500),
users: z.number().default(1),
price: z.number().default(49),
}).default({}),
professional: z.object({
websites: z.number().default(25),
scansPerMonth: z.number().default(5000),
users: z.number().default(5),
price: z.number().default(149),
}).default({}),
enterprise: z.object({
websites: z.number().default(-1), // Unlimited
scansPerMonth: z.number().default(-1),
users: z.number().default(-1),
price: z.number().default(499),
}).default({}),
}).default({}),
}).default({}),
});
export type WcagAppConfig = z.infer<typeof wcagAppConfigSchema>;

View file

@ -0,0 +1,49 @@
import { z } from 'zod';
export const workerConfigSchema = z.object({
enabled: z.boolean().default(true),
concurrency: z.number().min(1).max(10).default(2),
queueName: z.string().default('accessibility-scans'),
redis: z.object({
host: z.string().default('localhost'),
port: z.number().default(6379),
password: z.string().optional(),
db: z.number().default(2), // Different DB for WCAG
maxRetriesPerRequest: z.number().nullable().default(null),
}).default({}),
jobs: z.object({
scan: z.object({
priority: z.number().default(0),
attempts: z.number().default(3),
backoff: z.object({
type: z.enum(['exponential', 'fixed']).default('exponential'),
delay: z.number().default(2000),
}).default({}),
timeout: z.number().default(300000), // 5 minutes
removeOnComplete: z.object({
age: z.number().default(86400), // 24 hours
count: z.number().default(100),
}).default({}),
removeOnFail: z.object({
age: z.number().default(604800), // 7 days
count: z.number().default(500),
}).default({}),
}).default({}),
report: z.object({
priority: z.number().default(1),
attempts: z.number().default(2),
backoff: z.object({
type: z.enum(['exponential', 'fixed']).default('exponential'),
delay: z.number().default(5000),
}).default({}),
timeout: z.number().default(600000), // 10 minutes
}).default({}),
}).default({}),
scheduler: z.object({
enabled: z.boolean().default(true),
interval: z.number().default(60000), // Check every minute
timezone: z.string().default('UTC'),
}).default({}),
});
export type WorkerConfig = z.infer<typeof workerConfigSchema>;

View file

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"],
"references": [
{ "path": "../../../libs/core/config" }
]
}