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