UNPKG

@fission-ai/openspec

Version:

AI-native system for spec-driven development

115 lines 4.19 kB
import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; // Constants export const GLOBAL_CONFIG_DIR_NAME = 'openspec'; export const GLOBAL_CONFIG_FILE_NAME = 'config.json'; export const GLOBAL_DATA_DIR_NAME = 'openspec'; const DEFAULT_CONFIG = { featureFlags: {} }; /** * Gets the global configuration directory path following XDG Base Directory Specification. * * - All platforms: $XDG_CONFIG_HOME/openspec/ if XDG_CONFIG_HOME is set * - Unix/macOS fallback: ~/.config/openspec/ * - Windows fallback: %APPDATA%/openspec/ */ export function getGlobalConfigDir() { // XDG_CONFIG_HOME takes precedence on all platforms when explicitly set const xdgConfigHome = process.env.XDG_CONFIG_HOME; if (xdgConfigHome) { return path.join(xdgConfigHome, GLOBAL_CONFIG_DIR_NAME); } const platform = os.platform(); if (platform === 'win32') { // Windows: use %APPDATA% const appData = process.env.APPDATA; if (appData) { return path.join(appData, GLOBAL_CONFIG_DIR_NAME); } // Fallback for Windows if APPDATA is not set return path.join(os.homedir(), 'AppData', 'Roaming', GLOBAL_CONFIG_DIR_NAME); } // Unix/macOS fallback: ~/.config return path.join(os.homedir(), '.config', GLOBAL_CONFIG_DIR_NAME); } /** * Gets the global data directory path following XDG Base Directory Specification. * Used for user data like schema overrides. * * - All platforms: $XDG_DATA_HOME/openspec/ if XDG_DATA_HOME is set * - Unix/macOS fallback: ~/.local/share/openspec/ * - Windows fallback: %LOCALAPPDATA%/openspec/ */ export function getGlobalDataDir() { // XDG_DATA_HOME takes precedence on all platforms when explicitly set const xdgDataHome = process.env.XDG_DATA_HOME; if (xdgDataHome) { return path.join(xdgDataHome, GLOBAL_DATA_DIR_NAME); } const platform = os.platform(); if (platform === 'win32') { // Windows: use %LOCALAPPDATA% const localAppData = process.env.LOCALAPPDATA; if (localAppData) { return path.join(localAppData, GLOBAL_DATA_DIR_NAME); } // Fallback for Windows if LOCALAPPDATA is not set return path.join(os.homedir(), 'AppData', 'Local', GLOBAL_DATA_DIR_NAME); } // Unix/macOS fallback: ~/.local/share return path.join(os.homedir(), '.local', 'share', GLOBAL_DATA_DIR_NAME); } /** * Gets the path to the global config file. */ export function getGlobalConfigPath() { return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE_NAME); } /** * Loads the global configuration from disk. * Returns default configuration if file doesn't exist or is invalid. * Merges loaded config with defaults to ensure new fields are available. */ export function getGlobalConfig() { const configPath = getGlobalConfigPath(); try { if (!fs.existsSync(configPath)) { return { ...DEFAULT_CONFIG }; } const content = fs.readFileSync(configPath, 'utf-8'); const parsed = JSON.parse(content); // Merge with defaults (loaded values take precedence) return { ...DEFAULT_CONFIG, ...parsed, // Deep merge featureFlags featureFlags: { ...DEFAULT_CONFIG.featureFlags, ...(parsed.featureFlags || {}) } }; } catch (error) { // Log warning for parse errors, but not for missing files if (error instanceof SyntaxError) { console.error(`Warning: Invalid JSON in ${configPath}, using defaults`); } return { ...DEFAULT_CONFIG }; } } /** * Saves the global configuration to disk. * Creates the config directory if it doesn't exist. */ export function saveGlobalConfig(config) { const configDir = getGlobalConfigDir(); const configPath = getGlobalConfigPath(); // Create directory if it doesn't exist if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8'); } //# sourceMappingURL=global-config.js.map