Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
128
admin-ui/js/core/config-loader.js
Normal file
128
admin-ui/js/core/config-loader.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Configuration Loader - Secure Configuration Loading Pattern
|
||||
*
|
||||
* This implements the expert-recommended blocking initialization pattern:
|
||||
* 1. loadConfig() fetches from /api/config and stores it
|
||||
* 2. getConfig() returns config or throws if not loaded
|
||||
* 3. Application only initializes after loadConfig() completes
|
||||
*
|
||||
* This prevents race conditions where components try to access config
|
||||
* before it's been fetched from the server.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Module-scoped variable to hold the fetched server configuration.
|
||||
* @type {Object|null}
|
||||
*/
|
||||
let serverConfig = null;
|
||||
|
||||
/**
|
||||
* Fetches configuration from the server and stores it.
|
||||
* This MUST be called before the application initializes any components
|
||||
* that depend on the configuration.
|
||||
*
|
||||
* @async
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If the config endpoint is unreachable or returns an error
|
||||
*/
|
||||
export async function loadConfig() {
|
||||
// Prevent double-loading
|
||||
if (serverConfig) {
|
||||
console.warn('[ConfigLoader] Configuration already loaded.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned status ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
serverConfig = await response.json();
|
||||
|
||||
console.log('[ConfigLoader] Configuration loaded successfully', {
|
||||
dssHost: serverConfig.dssHost,
|
||||
dssPort: serverConfig.dssPort,
|
||||
storybookPort: serverConfig.storybookPort,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('[ConfigLoader] Failed to load configuration:', error);
|
||||
// Re-throw to be caught by the bootstrap function
|
||||
throw new Error(`Failed to load server configuration: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entire configuration object.
|
||||
* MUST ONLY be called after loadConfig() has completed successfully.
|
||||
*
|
||||
* @returns {Object} The server configuration
|
||||
* @throws {Error} If called before loadConfig() has completed
|
||||
*/
|
||||
export function getConfig() {
|
||||
if (!serverConfig) {
|
||||
throw new Error('[ConfigLoader] getConfig() called before configuration was loaded. Did you forget to await loadConfig()?');
|
||||
}
|
||||
return serverConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience getter for just the DSS host.
|
||||
* @returns {string} The DSS host
|
||||
*/
|
||||
export function getDssHost() {
|
||||
const config = getConfig();
|
||||
return config.dssHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience getter for DSS port.
|
||||
* @returns {string} The DSS port
|
||||
*/
|
||||
export function getDssPort() {
|
||||
const config = getConfig();
|
||||
return config.dssPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience getter for Storybook port.
|
||||
* @returns {number} The Storybook port (always 6006)
|
||||
*/
|
||||
export function getStorybookPort() {
|
||||
const config = getConfig();
|
||||
return config.storybookPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the full Storybook URL from config.
|
||||
* Points to Storybook running on port 6006 on the current host.
|
||||
*
|
||||
* @returns {string} The full Storybook URL (e.g., "http://dss.overbits.luz.uy:6006")
|
||||
*/
|
||||
export function getStorybookUrl() {
|
||||
const dssHost = getDssHost();
|
||||
const protocol = window.location.protocol; // "http:" or "https:"
|
||||
// Point to Storybook on port 6006
|
||||
return `${protocol}//${dssHost}:6006`;
|
||||
}
|
||||
|
||||
/**
|
||||
* TESTING ONLY: Reset the configuration state
|
||||
* This allows tests to load different configurations
|
||||
* @internal
|
||||
*/
|
||||
export function __resetForTesting() {
|
||||
serverConfig = null;
|
||||
}
|
||||
|
||||
export default {
|
||||
loadConfig,
|
||||
getConfig,
|
||||
getDssHost,
|
||||
getDssPort,
|
||||
getStorybookPort,
|
||||
getStorybookUrl,
|
||||
__resetForTesting,
|
||||
};
|
||||
Reference in New Issue
Block a user