Skip to content

Commit 807d946

Browse files
committed
feat: add deep env variable support
chore: wip chore: wip
1 parent 352cbfe commit 807d946

15 files changed

+1076
-60
lines changed

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- 🌐 **Universal**: _optimized for both Bun & browser environments_
1717
- 🪶 **Lightweight**: _zero dependencies, built on native modules_
1818
- 💪 **Type-Safe**: _fully typed configurations with generated type definitions_
19+
- 🌍 **Environment Variables**: _automatic environment variable support based on config name_
1920
- 🛠️ **CLI Tools**: _powerful & easy-to-use CLI_
2021
- 📦 **Flexible**: _supports multiple config file formats (.ts, .js, .mjs, .cjs, .json, .mts, .cts)_
2122

@@ -57,6 +58,60 @@ console.log(resolvedConfig) // { port: 3000, host: 'localhost' }, unless a confi
5758
> [!TIP]
5859
> If your `process.cwd()` includes a `$name.config.{ts,js,mjs,cjs,json}` _(or `.$name.config.{ts,js,mjs,cjs,json}`)_ file, it will be loaded and merged with defaults, where file config file values take precedence. For minimalists, it also loads a `.$name.{ts,js,mjs,cjs,json}` and `$name.{ts,js,mjs,cjs,json}` file if present.
5960
61+
### Environment Variables
62+
63+
Bunfig automatically checks for environment variables based on the config name. Environment variables take precedence over default values but are overridden by config files.
64+
65+
You can disable this feature by setting `checkEnv: false` in your config options:
66+
67+
```ts
68+
const options = {
69+
name: 'my-app',
70+
defaultConfig: { /* ... */ },
71+
checkEnv: false, // Disable environment variable checking
72+
}
73+
```
74+
75+
The naming convention for environment variables is:
76+
```
77+
[CONFIG_NAME]_[PROPERTY_NAME]
78+
```
79+
80+
For nested properties, use underscores to separate the levels:
81+
```
82+
[CONFIG_NAME]_[NESTED_PROPERTY_PATH]
83+
```
84+
85+
Example:
86+
87+
```ts
88+
// With a config name of "my-app"
89+
const options = {
90+
name: 'my-app',
91+
defaultConfig: {
92+
port: 3000,
93+
host: 'localhost',
94+
database: {
95+
url: 'postgres://localhost:5432',
96+
user: 'admin',
97+
},
98+
},
99+
}
100+
101+
// These environment variables would be automatically used if set:
102+
// MY_APP_PORT=8080
103+
// MY_APP_HOST=example.com
104+
// MY_APP_DATABASE_URL=postgres://production:5432
105+
// MY_APP_DATABASE_USER=prod_user
106+
```
107+
108+
For array values, you can use a JSON string or comma-separated values:
109+
```
110+
MY_APP_ALLOWED_ORIGINS=["https://example.com","https://api.example.com"]
111+
// or
112+
MY_APP_ALLOWED_ORIGINS=https://example.com,https://api.example.com
113+
```
114+
60115
### Browser Environment
61116

62117
For browser environments, use the `loadConfig` function from the browser-specific entry point to load your configuration from an API endpoint:

bun.lock

Lines changed: 109 additions & 23 deletions
Large diffs are not rendered by default.

docs/.vitepress/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { HeadConfig } from 'vitepress'
22
import { transformerTwoslash } from '@shikijs/vitepress-twoslash'
33
import { withPwa } from '@vite-pwa/vitepress'
44
import { defineConfig } from 'vitepress'
5-
65
import vite from './vite.config'
76

87
// https://vitepress.dev/reference/site-config
@@ -62,6 +61,7 @@ const sidebar = [
6261
text: 'Features',
6362
items: [
6463
{ text: 'Configuration Loading', link: '/features/configuration-loading' },
64+
{ text: 'Environment Variables', link: '/features/environment-variables' },
6565
{ text: 'Type Safety', link: '/features/type-safety' },
6666
],
6767
},

docs/advanced/browser-support.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,52 @@ const config = await loadConfig<MyConfig>({
2424
})
2525
```
2626

27+
### Environment Variables in Browser Context
28+
29+
While the automatic environment variable loading feature works great in server-side environments, browsers don't have direct access to system environment variables. In browser environments:
30+
31+
1. Environment variables can only be accessed if they are:
32+
- Embedded during the build process by tools like Vite or webpack (e.g., replacing `process.env.API_URL` with the actual value)
33+
- Made available through the API endpoint that serves your configuration
34+
35+
2. The `checkEnv` option is supported for API consistency, but has little effect unless you've embedded environment variables at build time
36+
37+
3. Common patterns for using environment-specific configuration in browsers include:
38+
- Using different API endpoints for different environments
39+
- Having the server inject environment variables into the initial page
40+
- Using build-time environment variables for configuration generation
41+
42+
```ts
43+
// In a browser context with build tool that supports env variables
44+
// If you're using Vite, for example, import.meta.env would contain these values
45+
const apiEndpoint = process.env.API_ENDPOINT || '/api/config'
46+
47+
const config = await loadConfig<MyConfig>({
48+
name: 'my-app',
49+
endpoint: apiEndpoint,
50+
defaultConfig: { /* ... */ },
51+
})
52+
```
53+
54+
For a universal (isomorphic) approach that works in both server and browser contexts, you can implement a pattern like this:
55+
56+
```ts
57+
import { isBrowser } from 'bunfig/browser'
58+
59+
async function getConfig() {
60+
if (isBrowser()) {
61+
// Browser: load from API
62+
const { loadConfig } = await import('bunfig/browser')
63+
return loadConfig({ endpoint: '/api/config', /* ... */ })
64+
}
65+
else {
66+
// Server: use file-based config with env vars
67+
const { loadConfig } = await import('bunfig')
68+
return loadConfig({ name: 'my-app', /* ... */ })
69+
}
70+
}
71+
```
72+
2773
### Custom Headers
2874

2975
You can include custom headers in your configuration requests:

docs/features/configuration-loading.md

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
bunfig provides a powerful and flexible configuration loading system that automatically finds and loads your configuration files from multiple possible locations and formats.
44

5+
## Configuration Resolution Order
6+
7+
bunfig resolves configuration in a priority order, giving you flexibility across different environments:
8+
9+
1. Default configuration values (provided in code)
10+
2. Environment variables (automatically detected based on config name)
11+
3. Configuration files (loaded from the filesystem)
12+
13+
This order ensures that values from higher priority sources override lower priority ones, allowing for a layered configuration approach.
14+
515
## Smart File Resolution
616

717
bunfig will search for your configuration file in the following order:
@@ -19,6 +29,23 @@ For example, if your `name` is "my-app", it will look for:
1929
- `.my-app.ts`
2030
(and the same for other supported extensions)
2131

32+
## Environment Variable Support
33+
34+
bunfig automatically checks for environment variables based on your configuration name, making it easy to override settings in different environments. Environment variables follow this naming pattern:
35+
36+
```
37+
[CONFIG_NAME]_[PROPERTY_NAME]
38+
```
39+
40+
For example, with a config name of "my-app", these environment variables would be recognized:
41+
42+
```bash
43+
MY_APP_PORT=8080
44+
MY_APP_HOST=production.example.com
45+
```
46+
47+
Environment variables are automatically converted to the correct type based on your default configuration. See the [Environment Variables](./environment-variables.md) section for more details.
48+
2249
## Supported Formats
2350

2451
bunfig supports multiple configuration file formats:
@@ -29,7 +56,7 @@ bunfig supports multiple configuration file formats:
2956

3057
## Deep Merging
3158

32-
When loading configuration, bunfig intelligently merges your default configuration with the values from your configuration file:
59+
When loading configuration, bunfig intelligently merges your default configuration with the values from environment variables and your configuration file:
3360

3461
```ts
3562
// Default configuration
@@ -43,10 +70,13 @@ const defaultConfig = {
4370
},
4471
}
4572

73+
// Environment variables
74+
// MY_APP_SERVER_PORT=8080
75+
4676
// my-app.config.ts
4777
export default {
4878
server: {
49-
port: 8080,
79+
host: 'custom.example.com',
5080
},
5181
features: {
5282
auth: true,
@@ -56,8 +86,8 @@ export default {
5686
// Result after merging
5787
const result = {
5888
server: {
59-
port: 8080, // From config file
60-
host: 'localhost', // From default config
89+
port: 8080, // From environment variable
90+
host: 'custom.example.com', // From config file (overrides env var if set)
6191
},
6292
features: {
6393
auth: true, // From config file
@@ -69,9 +99,9 @@ const result = {
6999

70100
bunfig handles various error scenarios gracefully:
71101

72-
- Missing configuration files: Returns the default configuration
102+
- Missing configuration files: Returns the default configuration with environment variables applied
73103
- Invalid file formats: Skips the file and continues searching
74-
- Type mismatches: Returns the default configuration
104+
- Type mismatches: Returns the default configuration with environment variables applied
75105
- File system errors: Properly caught and handled
76106

77107
## Working Directory Support
@@ -89,3 +119,15 @@ const config = await loadConfig<MyConfig>({
89119
```
90120

91121
This allows you to organize your configuration files in a dedicated directory structure.
122+
123+
## Disabling Features
124+
125+
You can disable certain features of the configuration loader if needed:
126+
127+
```ts
128+
const config = await loadConfig<MyConfig>({
129+
name: 'my-app',
130+
defaultConfig: { /* ... */ },
131+
checkEnv: false, // Disable environment variable loading
132+
})
133+
```
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# Environment Variables
2+
3+
Bunfig automatically checks for environment variables based on the config name. This makes it easy to override configuration values without modifying your config files, which is particularly useful for:
4+
5+
- Different deployment environments (development, staging, production)
6+
- CI/CD pipelines
7+
- Containerized applications
8+
- Secrets management
9+
10+
## How It Works
11+
12+
Environment variables take precedence over default values but are overridden by config files. This priority order allows for flexible configuration:
13+
14+
1. Default values (lowest priority)
15+
2. Environment variables (middle priority)
16+
3. Config files (highest priority)
17+
18+
## Naming Convention
19+
20+
The naming convention for environment variables follows this pattern:
21+
22+
```
23+
[CONFIG_NAME]_[PROPERTY_NAME]
24+
```
25+
26+
For nested properties, use underscores to separate the levels:
27+
28+
```
29+
[CONFIG_NAME]_[NESTED_PROPERTY_PATH]
30+
```
31+
32+
All keys are automatically converted to uppercase with hyphens replaced by underscores.
33+
34+
### Examples
35+
36+
With a config name of "my-app" and the following default configuration:
37+
38+
```ts
39+
const options = {
40+
name: 'my-app',
41+
defaultConfig: {
42+
port: 3000,
43+
host: 'localhost',
44+
database: {
45+
url: 'postgres://localhost:5432',
46+
user: 'admin',
47+
},
48+
features: {
49+
logging: {
50+
enabled: true,
51+
level: 'info',
52+
},
53+
},
54+
},
55+
}
56+
```
57+
58+
These environment variables would be automatically detected:
59+
60+
```bash
61+
# Top-level properties
62+
MY_APP_PORT=8080
63+
MY_APP_HOST=example.com
64+
65+
# Nested properties
66+
MY_APP_DATABASE_URL=postgres://production:5432
67+
MY_APP_DATABASE_USER=prod_user
68+
MY_APP_FEATURES_LOGGING_ENABLED=false
69+
MY_APP_FEATURES_LOGGING_LEVEL=error
70+
```
71+
72+
## Data Type Conversion
73+
74+
Bunfig automatically converts environment variables to the appropriate type based on the default value:
75+
76+
- **Numbers**: Environment variable values are converted to numbers
77+
- **Booleans**: `"true"` (case-insensitive) is converted to `true`, everything else to `false`
78+
- **Arrays**: Two formats are supported:
79+
- JSON arrays: `MY_APP_ALLOWED_ORIGINS=["https://example.com","https://api.example.com"]`
80+
- Comma-separated values: `MY_APP_ALLOWED_ORIGINS=https://example.com,https://api.example.com`
81+
- **Strings**: Used as-is
82+
83+
## Disabling Environment Variable Support
84+
85+
You can disable environment variable checking by setting `checkEnv: false` in your config options:
86+
87+
```ts
88+
const options = {
89+
name: 'my-app',
90+
defaultConfig: { /* ... */ },
91+
checkEnv: false, // Disable environment variable checking
92+
}
93+
```
94+
95+
## Browser Support
96+
97+
In browser environments, environment variables function differently since browser JavaScript doesn't have direct access to system environment variables. In this context:
98+
99+
- Server-side environment variables can be embedded during build time
100+
- You can pass environment configuration via the API endpoint specified in `loadConfig`
101+
102+
For browser applications, consider using environment variables during your build process to configure the endpoint URL:
103+
104+
```ts
105+
const config = await loadConfig({
106+
name: 'my-app',
107+
endpoint: process.env.API_ENDPOINT || '/api/config',
108+
defaultConfig: { /* ... */ },
109+
})
110+
```
111+
112+
## Best Practices
113+
114+
1. **Use environment variables for environment-specific settings**: ports, hosts, API keys, feature flags
115+
2. **Don't use environment variables for complex objects**: They work best for primitive values or simple arrays
116+
3. **Document your environment variables**: Include all supported environment variables in your README
117+
4. **Provide sensible defaults**: Make sure your application works with the default configuration
118+
119+
## Example: Complete Configuration Flow
120+
121+
```ts
122+
// 1. Default configuration in code
123+
const defaultConfig = {
124+
port: 3000,
125+
debug: false,
126+
api: {
127+
url: 'https://api.example.com',
128+
timeout: 5000,
129+
},
130+
}
131+
132+
// 2. Environment variables can override defaults
133+
// MY_APP_PORT=8080
134+
// MY_APP_API_URL=https://staging-api.example.com
135+
136+
// 3. Config file has highest priority (my-app.config.ts)
137+
export default {
138+
debug: true,
139+
api: {
140+
timeout: 10000,
141+
},
142+
}
143+
144+
// Final resolved configuration:
145+
{
146+
port: 8080, // From environment variable
147+
debug: true, // From config file
148+
api: {
149+
url: 'https://staging-api.example.com', // From environment variable
150+
timeout: 10000, // From config file
151+
},
152+
}
153+
```

0 commit comments

Comments
 (0)