All files / utils config-manager.js

5.26% Statements 4/76
0% Branches 0/34
0% Functions 0/13
5.33% Lines 4/75

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205          2x 2x 2x                                                                                                                                                                                                                                                                                                                                                                                                         2x
/**
 * Configuration Manager
 * Handles CLI configuration storage and retrieval
 */
 
const fs = require('fs-extra')
const path = require('path')
const os = require('os')
 
class ConfigManager {
  static getConfigPath() {
    // Use XDG config directory or fallback to home
    const configDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config')
    return path.join(configDir, 'vaultace', 'config.json')
  }
 
  static getConfig() {
    try {
      const configPath = this.getConfigPath()
      
      if (!fs.existsSync(configPath)) {
        return this.getDefaultConfig()
      }
      
      const config = fs.readJsonSync(configPath)
      
      // Merge with defaults to ensure all keys exist
      return {
        ...this.getDefaultConfig(),
        ...config
      }
      
    } catch (error) {
      console.warn(`Config read error: ${error.message}`)
      return this.getDefaultConfig()
    }
  }
 
  static getDefaultConfig() {
    return {
      apiUrl: 'https://api.vaultace.co',
      auth: {
        accessToken: null,
        refreshToken: null,
        expiresAt: null
      },
      defaults: {
        severity: 'medium',
        format: 'table',
        aiPatterns: true,
        respectGitignore: true
      },
      preferences: {
        autoUpdate: true,
        verbose: false,
        colorOutput: true
      }
    }
  }
 
  static saveConfig(config) {
    try {
      const configPath = this.getConfigPath()
      
      // Ensure config directory exists
      fs.ensureDirSync(path.dirname(configPath))
      
      // Write config with proper permissions
      fs.writeJsonSync(configPath, config, { spaces: 2, mode: 0o600 })
      
    } catch (error) {
      throw new Error(`Failed to save config: ${error.message}`)
    }
  }
 
  static set(key, value) {
    const config = this.getConfig()
    
    // Handle nested keys (e.g., "defaults.severity")
    const keys = key.split('.')
    let current = config
    
    for (let i = 0; i < keys.length - 1; i++) {
      const k = keys[i]
      if (!current[k] || typeof current[k] !== 'object') {
        current[k] = {}
      }
      current = current[k]
    }
    
    current[keys[keys.length - 1]] = value
    
    this.saveConfig(config)
  }
 
  static get(key) {
    const config = this.getConfig()
    
    // Handle nested keys
    const keys = key.split('.')
    let current = config
    
    for (const k of keys) {
      if (current && typeof current === 'object' && k in current) {
        current = current[k]
      } else {
        return undefined
      }
    }
    
    return current
  }
 
  static setAuth(authData) {
    const config = this.getConfig()
    config.auth = {
      ...config.auth,
      ...authData
    }
    this.saveConfig(config)
  }
 
  static clearAuth() {
    const config = this.getConfig()
    config.auth = {
      accessToken: null,
      refreshToken: null,
      expiresAt: null
    }
    this.saveConfig(config)
  }
 
  static isAuthenticated() {
    const config = this.getConfig()
    return !!(config.auth?.accessToken && config.auth?.refreshToken)
  }
 
  static isTokenExpired() {
    const config = this.getConfig()
    
    if (!config.auth?.expiresAt) {
      return true
    }
    
    // Add 5 minute buffer for token refresh
    return Date.now() > (config.auth.expiresAt - 300000)
  }
 
  static reset() {
    const defaultConfig = this.getDefaultConfig()
    this.saveConfig(defaultConfig)
  }
 
  static validateConfig(config = null) {
    const cfg = config || this.getConfig()
    
    // Validate API URL
    if (cfg.apiUrl) {
      try {
        new URL(cfg.apiUrl)
      } catch {
        throw new Error('Invalid API URL format')
      }
    }
    
    // Validate severity levels
    const validSeverities = ['low', 'medium', 'high', 'critical']
    if (cfg.defaults?.severity && !validSeverities.includes(cfg.defaults.severity)) {
      throw new Error(`Invalid severity level. Must be one of: ${validSeverities.join(', ')}`)
    }
    
    // Validate output formats
    const validFormats = ['table', 'json', 'csv', 'sarif']
    if (cfg.defaults?.format && !validFormats.includes(cfg.defaults.format)) {
      throw new Error(`Invalid output format. Must be one of: ${validFormats.join(', ')}`)
    }
    
    return true
  }
 
  // Migration helpers
  static migrate() {
    try {
      const config = this.getConfig()
      let migrated = false
      
      // Example migration: rename old keys
      if (config.api_url) {
        config.apiUrl = config.api_url
        delete config.api_url
        migrated = true
      }
      
      if (migrated) {
        this.saveConfig(config)
        console.log(chalk.blue('ℹ️ Configuration migrated to latest format'))
      }
      
    } catch (error) {
      console.warn(`Config migration warning: ${error.message}`)
    }
  }
}
 
module.exports = ConfigManager