( file: string, createDefault: () => A, throwOnInvalid?: boolean, )
| 1493 | } |
| 1494 | |
| 1495 | function getConfig<A>( |
| 1496 | file: string, |
| 1497 | createDefault: () => A, |
| 1498 | throwOnInvalid?: boolean, |
| 1499 | ): A { |
| 1500 | // Log a warning if config is accessed before it's allowed |
| 1501 | if (!configReadingAllowed && process.env.NODE_ENV !== 'test') { |
| 1502 | throw new Error('Config accessed before allowed.') |
| 1503 | } |
| 1504 | |
| 1505 | const fs = getFsImplementation() |
| 1506 | |
| 1507 | try { |
| 1508 | const fileContent = fs.readFileSync(file, { |
| 1509 | encoding: 'utf-8', |
| 1510 | }) |
| 1511 | try { |
| 1512 | // Strip BOM before parsing - PowerShell 5.x adds BOM to UTF-8 files |
| 1513 | const parsedConfig = jsonParse(stripBOM(fileContent)) |
| 1514 | return { |
| 1515 | ...createDefault(), |
| 1516 | ...parsedConfig, |
| 1517 | } |
| 1518 | } catch (error) { |
| 1519 | // Throw a ConfigParseError with the file path and default config |
| 1520 | const errorMessage = |
| 1521 | error instanceof Error ? error.message : String(error) |
| 1522 | throw new ConfigParseError(errorMessage, file, createDefault()) |
| 1523 | } |
| 1524 | } catch (error) { |
| 1525 | // Handle file not found - check for backup and return default |
| 1526 | const errCode = getErrnoCode(error) |
| 1527 | if (errCode === 'ENOENT') { |
| 1528 | const backupPath = findMostRecentBackup(file) |
| 1529 | maybeWarnMissingConfigOnce(file, backupPath) |
| 1530 | return createDefault() |
| 1531 | } |
| 1532 | |
| 1533 | // Re-throw ConfigParseError if throwOnInvalid is true |
| 1534 | if (error instanceof ConfigParseError && throwOnInvalid) { |
| 1535 | throw error |
| 1536 | } |
| 1537 | |
| 1538 | // Log config parse errors so users know what happened |
| 1539 | if (error instanceof ConfigParseError) { |
| 1540 | logForDebugging( |
| 1541 | `Config file corrupted, resetting to defaults: ${error.message}`, |
| 1542 | { level: 'error' }, |
| 1543 | ) |
| 1544 | |
| 1545 | // Guard: logEvent → shouldSampleEvent → getGlobalConfig → getConfig |
| 1546 | // causes infinite recursion when the config file is corrupted, because |
| 1547 | // the sampling check reads a GrowthBook feature from global config. |
| 1548 | // Only log analytics on the outermost call. |
| 1549 | if (!insideGetConfig) { |
| 1550 | insideGetConfig = true |
| 1551 | try { |
| 1552 | // Log the error for monitoring |
no test coverage detected