Skip to main content

Function signature

packages/core/src/config-loader.ts:56-63
export async function loadConfig(cwd = process.cwd()): Promise<BunliConfig> {
  const result = await loadConfigResult(cwd)
  if (result.isOk()) {
    return result.value
  }

  throw result.error
}
Load configuration from a Bunli config file. The function searches for configuration files in the specified directory and returns a validated config object.

Parameters

cwd
string
default:"process.cwd()"
Directory to search for configuration files. Defaults to the current working directory.

Return value

Promise<BunliConfig>
Promise<BunliConfig>
Promise that resolves to a validated configuration object with all defaults applied.
The function searches for configuration files in this order:
  1. bunli.config.ts (TypeScript)
  2. bunli.config.js (JavaScript CommonJS)
  3. bunli.config.mjs (JavaScript ESM)
The first file found is loaded and validated.

Error handling

The function throws errors in two cases:

ConfigNotFoundError

Thrown when no configuration file is found in the specified directory.
ConfigNotFoundError
TaggedError
try {
  const config = await loadConfig('./my-project')
} catch (error) {
  if (error instanceof ConfigNotFoundError) {
    console.error('No config file found')
    console.error('Searched for:', error.searched)
  }
}

ConfigLoadError

Thrown when a configuration file is found but fails to load or validate.
ConfigLoadError
TaggedError
try {
  const config = await loadConfig()
} catch (error) {
  if (error instanceof ConfigLoadError) {
    console.error(`Failed to load config from ${error.path}`)
    console.error('Cause:', error.cause)
  }
}

Result-based API

For more control over error handling, use loadConfigResult() which returns a Result type instead of throwing:
packages/core/src/config-loader.ts:27-54
export async function loadConfigResult(
  cwd = process.cwd()
): Promise<Result<BunliConfig, ConfigNotFoundError | ConfigLoadError>>

Examples with Result API

import { loadConfigResult } from '@bunli/core'

const result = await loadConfigResult()

if (result.isOk()) {
  const config = result.value
  console.log(`Loaded config for ${config.name}`)
} else {
  const error = result.error
  
  if (error instanceof ConfigNotFoundError) {
    console.log('No config file found, using defaults')
  } else if (error instanceof ConfigLoadError) {
    console.error(`Failed to load config: ${error.message}`)
    process.exit(1)
  }
}

Examples

Basic usage

import { loadConfig } from '@bunli/core'

// Load from current directory
const config = await loadConfig()

console.log(config.name)     // CLI name
console.log(config.version)  // CLI version

Load from specific directory

import { loadConfig } from '@bunli/core'
import path from 'node:path'

const projectDir = path.join(process.cwd(), 'my-project')
const config = await loadConfig(projectDir)

With error handling

import { loadConfig, ConfigNotFoundError, ConfigLoadError } from '@bunli/core'

try {
  const config = await loadConfig()
  console.log(`Loaded ${config.name} v${config.version}`)
} catch (error) {
  if (error instanceof ConfigNotFoundError) {
    console.error('No bunli config file found')
    console.error('Please create one of:', error.searched.join(', '))
    process.exit(1)
  }
  
  if (error instanceof ConfigLoadError) {
    console.error(`Failed to load config from ${error.path}`)
    console.error(error.message)
    process.exit(1)
  }
  
  throw error
}

Integration with createCLI

Note that createCLI() automatically calls loadConfig() internally, so you typically don’t need to load config manually:
import { createCLI } from '@bunli/core'

// Config is automatically loaded
const cli = await createCLI()

// But you can also load it explicitly for other purposes
import { loadConfig } from '@bunli/core'
const config = await loadConfig()

// Then pass it to createCLI
const cli = await createCLI(config)

Conditional loading

import { loadConfigResult, defineConfig } from '@bunli/core'

const result = await loadConfigResult()

const config = result.isOk() 
  ? result.value
  : defineConfig({
      name: 'my-cli',
      version: '1.0.0',
      description: 'Fallback config'
    })

const cli = await createCLI(config)

Merging configurations

import { loadConfig, defineConfig } from '@bunli/core'

// Load base config from file
const baseConfig = await loadConfig()

// Merge with overrides
const config = defineConfig({
  ...baseConfig,
  // Override specific fields
  plugins: [
    ...baseConfig.plugins,
    myCustomPlugin()
  ]
})

const cli = await createCLI(config)

Debugging

The config loader uses the core:config logger namespace. Enable debug logs to see what’s happening:
DEBUG=core:config bun run cli.ts
This will output:
  • Which config files are being searched
  • Which file was found and loaded
  • Validation errors if the config is invalid

Configuration validation

All loaded configurations are validated using the bunliConfigSchema Zod schema. This ensures:
  • Required fields are present
  • Field types are correct
  • Default values are applied
  • Invalid values are rejected
// Valid config
{
  name: 'my-cli',
  version: '1.0.0'
}

// Invalid config (will throw ConfigLoadError)
{
  name: '',  // Empty string not allowed
  version: '1.0.0'
}

Build docs developers (and LLMs) love