( file: string, createDefault: () => A, throwOnInvalid?: boolean, )
| 1419 | } |
| 1420 | |
| 1421 | function getConfig<A>( |
| 1422 | file: string, |
| 1423 | createDefault: () => A, |
| 1424 | throwOnInvalid?: boolean, |
| 1425 | ): A { |
| 1426 | // Log a warning if config is accessed before it's allowed |
| 1427 | if (!configReadingAllowed && process.env.NODE_ENV !== 'test') { |
| 1428 | throw new Error('Config accessed before allowed.') |
| 1429 | } |
| 1430 | |
| 1431 | const fs = getFsImplementation() |
| 1432 | |
| 1433 | try { |
| 1434 | const fileContent = fs.readFileSync(file, { |
| 1435 | encoding: 'utf-8', |
| 1436 | }) |
| 1437 | try { |
| 1438 | // Strip BOM before parsing - PowerShell 5.x adds BOM to UTF-8 files |
| 1439 | const parsedConfig = jsonParse(stripBOM(fileContent)) |
| 1440 | return { |
| 1441 | ...createDefault(), |
| 1442 | ...parsedConfig, |
| 1443 | } |
| 1444 | } catch (error) { |
| 1445 | // Throw a ConfigParseError with the file path and default config |
| 1446 | const errorMessage = |
| 1447 | error instanceof Error ? error.message : String(error) |
| 1448 | throw new ConfigParseError(errorMessage, file, createDefault()) |
| 1449 | } |
| 1450 | } catch (error) { |
| 1451 | // Handle file not found - check for backup and return default |
| 1452 | const errCode = getErrnoCode(error) |
| 1453 | if (errCode === 'ENOENT') { |
| 1454 | const backupPath = findMostRecentBackup(file) |
| 1455 | if (backupPath) { |
| 1456 | process.stderr.write( |
| 1457 | `\nClaude configuration file not found at: ${file}\n` + |
| 1458 | `A backup file exists at: ${backupPath}\n` + |
| 1459 | `You can manually restore it by running: cp "${backupPath}" "${file}"\n\n`, |
| 1460 | ) |
| 1461 | } |
| 1462 | return createDefault() |
| 1463 | } |
| 1464 | |
| 1465 | // Re-throw ConfigParseError if throwOnInvalid is true |
| 1466 | if (error instanceof ConfigParseError && throwOnInvalid) { |
| 1467 | throw error |
| 1468 | } |
| 1469 | |
| 1470 | // Log config parse errors so users know what happened |
| 1471 | if (error instanceof ConfigParseError) { |
| 1472 | logForDebugging( |
| 1473 | `Config file corrupted, resetting to defaults: ${error.message}`, |
| 1474 | { level: 'error' }, |
| 1475 | ) |
| 1476 | |
| 1477 | // Guard: logEvent → shouldSampleEvent → getGlobalConfig → getConfig |
| 1478 | // causes infinite recursion when the config file is corrupted, because |
no test coverage detected