Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion FrontEnd/my-app/check-env.js
Original file line number Diff line number Diff line change
@@ -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();
51 changes: 51 additions & 0 deletions FrontEnd/my-app/context/ConfigBoundaryContext.tsx
Original file line number Diff line number Diff line change
@@ -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<Props, State> {
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 (
<div className="p-4 m-4 border border-red-800 bg-red-950/20 text-red-400 rounded-lg">
<h3>⚠️ Configuration Error</h3>
<p className="text-sm font-mono mt-1">{this.state.errorMessage}</p>
</div>
);
}

return this.props.children;
}
}
Empty file.
41 changes: 41 additions & 0 deletions FrontEnd/my-app/lib/env-boundary.ts
Original file line number Diff line number Diff line change
@@ -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')
});
1 change: 1 addition & 0 deletions FrontEnd/my-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion FrontEnd/my-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -84,4 +85,4 @@
"@typescript-eslint/tsconfig-utils": "8.59.1",
"postcss": "8.5.10"
}
}
}
71 changes: 71 additions & 0 deletions FrontEnd/my-app/scripts/validate-env-parity.js
Original file line number Diff line number Diff line change
@@ -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();
Empty file.
Loading