diff --git a/FrontEnd/my-app/check-env.js b/FrontEnd/my-app/check-env.js index ba67c848c..ebdc33b62 100644 --- a/FrontEnd/my-app/check-env.js +++ b/FrontEnd/my-app/check-env.js @@ -1 +1,31 @@ -console.log('NEXT_PUBLIC_API_BASE_URL:', process.env.NEXT_PUBLIC_API_BASE_URL); +// FrontEnd/my-app/check-env.js + +const isProduction = process.env.NODE_ENV === 'production'; + +function runEnvironmentHygieneCheck() { + const missingVars = []; + + // Simulated configuration evaluation mapping + const requiredVars = ['NEXT_PUBLIC_STELLAR_NETWORK', 'NEXT_PUBLIC_HORIZON_URL']; + + requiredVars.forEach((key) => { + if (!process.env[key]) { + missingVars.push(key); + } + }); + + if (missingVars.length > 0) { + // Crucial errors that break execution can still log or throw, + // but generic diagnostic logs are strictly silenced. + if (!isProduction) { + console.warn(`⚠️ Environment Warning: Missing local variables: ${missingVars.join(', ')}`); + } + } else { + // Task Requirement: Remove debug console noise for production bundle hygiene + if (!isProduction) { + console.log('✅ Environment validation sequence completed successfully.'); + } + } +} + +runEnvironmentHygieneCheck(); \ No newline at end of file diff --git a/FrontEnd/my-app/context/ConfigBoundaryContext.tsx b/FrontEnd/my-app/context/ConfigBoundaryContext.tsx new file mode 100644 index 000000000..e63514d38 --- /dev/null +++ b/FrontEnd/my-app/context/ConfigBoundaryContext.tsx @@ -0,0 +1,51 @@ +'use client'; + +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { ConfigurationBoundaryError } from '../lib/env-boundary'; + +interface Props { + children: ReactNode; + fallbackBoundaryName: string; +} + +interface State { + hasError: boolean; + errorMessage: string | null; +} + +export class ConfigBoundaryGuard extends Component { + public state: State = { + hasError: false, + errorMessage: null + }; + + public static getDerivedStateFromError(error: Error): State { + if (error instanceof ConfigurationBoundaryError) { + return { hasError: true, errorMessage: error.message }; + } + throw error; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + // Task Requirement: Strip verbose console logging from environmental contexts in production + if (process.env.NODE_ENV !== 'production') { + console.error("Configuration Boundary Intercepted:", error, errorInfo); + } else { + // Send trace diagnostics to your telemetry endpoint securely instead of dumping to standard browser out + // myTelemetryClient.captureException(error); + } + } + + public render() { + if (this.state.hasError) { + return ( +
+

⚠️ Configuration Error

+

{this.state.errorMessage}

+
+ ); + } + + return this.props.children; + } +} \ No newline at end of file diff --git a/FrontEnd/my-app/lib/check-env.js b/FrontEnd/my-app/lib/check-env.js new file mode 100644 index 000000000..e69de29bb diff --git a/FrontEnd/my-app/lib/env-boundary.ts b/FrontEnd/my-app/lib/env-boundary.ts new file mode 100644 index 000000000..f394fe029 --- /dev/null +++ b/FrontEnd/my-app/lib/env-boundary.ts @@ -0,0 +1,41 @@ +export class ConfigurationBoundaryError extends Error { + constructor(public variableName: string, contextBoundary: string) { + super(`Configuration Error: [${contextBoundary}] Missing mandatory environment variable "${variableName}". Ensure it is specified in your .env configuration.`); + this.name = 'ConfigurationBoundaryError'; + } +} + +/** + * Validates and retrieves a target environment parameter at the active operational boundary. + * Prevents initialization crashes by throwing lazily only when requested. + */ +export const getEnvParamLazy = ( + key: string, + boundaryName: string, + fallback?: string +): string => { + const value = process.env[key]; + + if (!value) { + if (fallback !== undefined) { + return fallback; + } + // Task Requirement: Lazy runtime validation per boundary instead of global crashes + throw new ConfigurationBoundaryError(key, boundaryName); + } + + return value; +}; + +// ============================================================================ +// On-Demand Boundary Context Mappings +// ============================================================================ + +export const getStellarNetworkConfig = () => ({ + network: getEnvParamLazy('NEXT_PUBLIC_STELLAR_NETWORK', 'Stellar Network Connection', 'TESTNET'), + horizonUrl: getEnvParamLazy('NEXT_PUBLIC_HORIZON_URL', 'Stellar Network Connection') +}); + +export const getAnalyticsConfig = () => ({ + sentryDsn: getEnvParamLazy('NEXT_PUBLIC_SENTRY_DSN', 'Observability Analytics Module') +}); \ No newline at end of file diff --git a/FrontEnd/my-app/package-lock.json b/FrontEnd/my-app/package-lock.json index 658671e21..cdd541983 100644 --- a/FrontEnd/my-app/package-lock.json +++ b/FrontEnd/my-app/package-lock.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@axe-core/playwright": "^4.12.1", + "@eslint/eslintrc": "^3.1.0", "@next/bundle-analyzer": "^15.0.0", "@playwright/test": "^1.58.2", "@tailwindcss/postcss": "^4", diff --git a/FrontEnd/my-app/package.json b/FrontEnd/my-app/package.json index 37acabe30..7181e755a 100644 --- a/FrontEnd/my-app/package.json +++ b/FrontEnd/my-app/package.json @@ -15,6 +15,7 @@ "test:integration": "vitest run --include '**/*.integration.test.ts'", "test:watch": "vitest", "test:coverage": "vitest run --coverage", + "test:env-parity": "node scripts/validate-env-parity.js", "format": "prettier --write .", "format:check": "prettier --check .", "changelog:check": "node scripts/check-changelog.mjs", @@ -84,4 +85,4 @@ "@typescript-eslint/tsconfig-utils": "8.59.1", "postcss": "8.5.10" } -} +} \ No newline at end of file diff --git a/FrontEnd/my-app/scripts/validate-env-parity.js b/FrontEnd/my-app/scripts/validate-env-parity.js new file mode 100644 index 000000000..e2329a9af --- /dev/null +++ b/FrontEnd/my-app/scripts/validate-env-parity.js @@ -0,0 +1,71 @@ +const fs = require('fs'); +const path = require('path'); + +function validateEnvParity() { + const rootDir = path.resolve(__dirname, '..'); + const envExamplePath = path.join(rootDir, '.env.example'); + + if (!fs.existsSync(envExamplePath)) { + console.error('❌ Error: .env.example file was not found at the root level.'); + process.exit(1); + } + + // 1. Parse keys defined in .env.example + const envExampleContent = fs.readFileSync(envExamplePath, 'utf8'); + const definedKeys = new Set( + envExampleContent + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) + .map(line => line.split('=')[0].trim()) + ); + + // 2. Scan code files recursively for process.env usage + const targetDirs = ['app', 'components', 'context', 'lib']; + const usedKeys = new Set(); + const envRegex = /process\.env\.(NEXT_PUBLIC_[A-Z0-9_]+)/g; + + function scanDirectory(dirPath) { + const files = fs.readdirSync(dirPath); + + files.forEach(file => { + const fullPath = path.join(dirPath, file); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + if (file !== 'node_modules' && file !== '.next') { + scanDirectory(fullPath); + } + } else if (/\.(js|ts|tsx|jsx)$/.test(file)) { + const content = fs.readFileSync(fullPath, 'utf8'); + let match; + while ((match = envRegex.exec(content)) !== null) { + usedKeys.add(match[1]); + } + } + }); + } + + targetDirs.forEach(dir => { + const absolutePath = path.join(rootDir, dir); + if (fs.existsSync(absolutePath)) { + scanDirectory(absolutePath); + } + }); + + // 3. Find missing keys + const missingInExample = [...usedKeys].filter(key => !definedKeys.has(key)); + + if (missingInExample.length > 0) { + console.error('\n❌ CI Parity Validation Failed!'); + console.error('The following environment variables are used in the codebase but missing from .env.example:\n'); + missingInExample.forEach(key => console.error(` 👉 ${key}`)); + console.error('\nAction Required: Please append these keys to .env.example to restore pipeline alignment.\n'); + process.exit(1); + } + + console.log('✅ CI Success: .env.example parity match completely synchronized.'); + process.exit(0); +} + +validateEnvParity(); \ No newline at end of file diff --git a/FrontEnd/my-app/tests/env-boundary.spec.ts b/FrontEnd/my-app/tests/env-boundary.spec.ts new file mode 100644 index 000000000..e69de29bb