/**
* Configuration loader for PartnerCore Proxy
*/
import { config as dotenvConfig } from 'dotenv';
import * as path from 'path';
import * as fs from 'fs';
import { ProxyConfig, ALConfig } from './types.js';
/**
* Environment endpoints
*/
const ENDPOINTS = {
production: 'https://partnercore-mcp-prod.agreeablebush-0fe29c4e.eastus.azurecontainerapps.io',
uat: 'https://partnercore-mcp-uat.happysand-4085830a.eastus.azurecontainerapps.io',
} as const;
type Environment = 'production' | 'uat';
/**
* Get the cloud URL based on environment
*/
function getCloudUrl(): string {
// Explicit URL takes precedence
const cloudUrl = process.env['CLOUD_URL'] || process.env['PARTNERCORE_CLOUD_URL'];
if (cloudUrl) {
return cloudUrl;
}
// Check for environment setting
const env = (process.env['PARTNERCORE_ENV'] || 'production').toLowerCase() as Environment;
if (env === 'uat') {
return ENDPOINTS.uat;
}
return ENDPOINTS.production;
}
/**
* Load configuration from environment variables and defaults
*/
export function loadConfig(): ProxyConfig {
// Load .env file if present
dotenvConfig();
const al = loadALConfig();
// Get API key from environment (customer/resource looked up from Azure Tables)
const apiKey = process.env['API_KEY'] || '';
return {
cloudUrl: getCloudUrl(),
apiKey,
port: parseInt(process.env['PROXY_PORT'] || '3100', 10),
logLevel: (process.env['LOG_LEVEL'] as ProxyConfig['logLevel']) || 'info',
al,
};
}
/**
* Find AL workspace root by walking up the directory tree looking for app.json
* Similar to how VS Code and other tools detect workspace roots
*/
export function findALWorkspace(startDir?: string): string | null {
let currentDir = path.resolve(startDir || process.cwd());
const root = path.parse(currentDir).root;
// Walk up the directory tree
while (currentDir !== root) {
const appJsonPath = path.join(currentDir, 'app.json');
if (fs.existsSync(appJsonPath)) {
return currentDir;
}
// Move up one directory
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
break; // Reached root
}
currentDir = parentDir;
}
return null;
}
/**
* Load AL-specific configuration
*/
function loadALConfig(): ALConfig {
// Use explicit workspace root if provided, otherwise auto-detect
let workspaceRoot: string;
if (process.env['AL_WORKSPACE_ROOT']) {
workspaceRoot = path.resolve(process.env['AL_WORKSPACE_ROOT']);
} else {
// Auto-detect workspace by walking up from current directory
const detected = findALWorkspace();
workspaceRoot = detected || process.cwd();
}
const extensionCachePath = path.join(
process.env['HOME'] || process.env['USERPROFILE'] || '.',
'.partnercore',
'al-extension'
);
return {
extensionVersion: process.env['AL_EXTENSION_VERSION'] || 'latest',
workspaceRoot,
extensionPath: process.env['AL_EXTENSION_PATH'],
extensionCachePath,
};
}
/**
* Validate configuration
* Note: Workspace validation is lenient - workspace can be detected lazily when tools are used
*/
export function validateConfig(config: ProxyConfig): { valid: boolean; errors: string[]; warnings: string[] } {
const errors: string[] = [];
const warnings: string[] = [];
// API key is required for the MCP server to function
if (!config.apiKey) {
errors.push('API_KEY is required. Set the API_KEY environment variable.');
}
// Validate workspace root exists
if (!fs.existsSync(config.al.workspaceRoot)) {
warnings.push(`AL workspace root does not exist: ${config.al.workspaceRoot}`);
} else {
// Check for app.json in workspace
const appJsonPath = path.join(config.al.workspaceRoot, 'app.json');
if (!fs.existsSync(appJsonPath)) {
// Try to auto-detect workspace
const detected = findALWorkspace(config.al.workspaceRoot);
if (detected) {
warnings.push(`app.json not found in ${config.al.workspaceRoot}, but detected workspace at: ${detected}`);
} else {
warnings.push(`app.json not found in workspace: ${appJsonPath}. Workspace will be auto-detected when tools are used.`);
}
}
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Find app.json in workspace and return its contents
*/
export function loadAppJson(workspaceRoot: string): Record<string, unknown> | null {
const appJsonPath = path.join(workspaceRoot, 'app.json');
if (!fs.existsSync(appJsonPath)) {
return null;
}
try {
const content = fs.readFileSync(appJsonPath, 'utf-8');
return JSON.parse(content) as Record<string, unknown>;
} catch {
return null;
}
}