@idealyst/config 1.2.13 → 1.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,8 +6,8 @@ Cross-platform configuration for React and React Native with env inheritance sup
6
6
 
7
7
  - **Single API** - Same code works on web and native
8
8
  - **Env inheritance** - Shared config with platform-specific overrides
9
+ - **Babel plugin** - Config injected at build time, no runtime overhead
9
10
  - **Type-safe** - Auto-generated TypeScript declarations
10
- - **Bundler agnostic** - No bundler configuration needed
11
11
  - **Monorepo friendly** - Designed for shared/web/mobile patterns
12
12
 
13
13
  ## Installation
@@ -54,75 +54,97 @@ API_URL=https://web-api.example.com
54
54
  ANALYTICS_ENABLED=false
55
55
  ```
56
56
 
57
- ### 2. Generate config
58
-
59
- ```bash
60
- # In packages/web/
61
- npx idealyst-config generate --extends ../shared/.env --env .env
62
-
63
- # In packages/mobile/
64
- npx idealyst-config generate --extends ../shared/.env --env .env
57
+ ### 2. Add Babel plugin
58
+
59
+ **babel.config.js:**
60
+ ```js
61
+ module.exports = {
62
+ presets: ['...'],
63
+ plugins: [
64
+ ['@idealyst/config/plugin', {
65
+ extends: ['../shared/.env'],
66
+ envPath: '.env'
67
+ }]
68
+ ]
69
+ }
65
70
  ```
66
71
 
67
- This creates `src/config.generated.ts` with merged values:
72
+ ### 3. Use in your app
68
73
 
69
74
  ```typescript
70
- // Web: API_URL overridden, others inherited
71
- export const generatedConfig = {
72
- API_URL: "https://web-api.example.com",
73
- GOOGLE_CLIENT_ID: "abc123",
74
- ANALYTICS_ENABLED: "true"
75
- }
75
+ import { config } from '@idealyst/config'
76
+
77
+ // Values are injected at build time!
78
+ const apiUrl = config.get('API_URL')
79
+ const analyticsEnabled = config.get('ANALYTICS_ENABLED') === 'true'
76
80
  ```
77
81
 
78
- ### 3. Initialize and use
82
+ That's it! The Babel plugin reads your .env files at compile time and injects the values automatically.
79
83
 
80
- ```typescript
81
- // In your app entry point (e.g., App.tsx, main.tsx)
82
- import { setConfig } from '@idealyst/config'
83
- import { generatedConfig } from './config.generated'
84
+ ## Babel Plugin Options
84
85
 
85
- setConfig(generatedConfig)
86
- ```
86
+ ```js
87
+ ['@idealyst/config/plugin', {
88
+ // Inherit from these .env files (lowest to highest priority)
89
+ extends: ['../shared/.env', '../common/.env'],
87
90
 
88
- ```typescript
89
- // Anywhere in your app
90
- import { config } from '@idealyst/config'
91
+ // Main .env file (highest priority, default: auto-detect)
92
+ envPath: '.env',
91
93
 
92
- const apiUrl = config.get('API_URL')
93
- const analyticsEnabled = config.get('ANALYTICS_ENABLED') === 'true'
94
+ // Required keys - warn if missing at build time
95
+ required: ['API_URL', 'AUTH_SECRET'],
96
+
97
+ // Fail the build if required keys are missing (default: false = warn only)
98
+ errorOnMissing: true,
99
+
100
+ // Log which .env files were loaded (default: false)
101
+ verbose: true,
102
+
103
+ // Project root (default: process.cwd())
104
+ root: '/path/to/project'
105
+ }]
94
106
  ```
95
107
 
96
- ## CLI Reference
108
+ ### Build-time Validation
97
109
 
98
- ```bash
99
- idealyst-config generate [options]
110
+ The plugin validates required keys at compile time:
111
+
112
+ ```js
113
+ // babel.config.js
114
+ plugins: [
115
+ ['@idealyst/config/plugin', {
116
+ required: ['API_URL', 'DATABASE_URL', 'JWT_SECRET'],
117
+ errorOnMissing: process.env.NODE_ENV === 'production' // Fail in prod, warn in dev
118
+ }]
119
+ ]
120
+ ```
100
121
 
101
- Options:
102
- --env <path> Path to .env file (default: auto-detect)
103
- --extends <path> Inherit from another .env (can use multiple times)
104
- --output <path> Output path (default: src/config.generated.ts)
105
- --types-only Generate only .d.ts file, no values
106
- --help Show help
122
+ **Warning output:**
123
+ ```
124
+ @idealyst/config: Missing required config keys: JWT_SECRET
125
+ Add these keys to your .env file to suppress this warning.
126
+ Set errorOnMissing: true to fail the build instead.
107
127
  ```
108
128
 
109
- ### Examples
129
+ **Error output (with `errorOnMissing: true`):**
130
+ ```
131
+ ✖ @idealyst/config: Missing required config keys: JWT_SECRET
132
+ Add these keys to your .env file or check your plugin configuration.
110
133
 
111
- ```bash
112
- # Simple - auto-detect .env
113
- idealyst-config generate
134
+ Error: @idealyst/config: Missing required config keys: JWT_SECRET
135
+ ```
114
136
 
115
- # With shared inheritance
116
- idealyst-config generate --extends ../shared/.env --env .env
137
+ ### Auto-detection
117
138
 
118
- # Multiple inheritance (lowest to highest priority)
119
- idealyst-config generate \
120
- --extends ../../shared/.env \
121
- --extends ../.env.common \
122
- --env .env
139
+ If you don't specify options, the plugin will:
140
+ 1. Auto-detect `.env.local`, `.env.development`, or `.env` in your project
141
+ 2. Auto-detect `../shared/.env` or `../../shared/.env` for inheritance
123
142
 
124
- # Types only (for CI/type checking without values)
125
- idealyst-config generate --types-only --output src/env.d.ts
143
+ ```js
144
+ // Minimal config - auto-detects everything
145
+ plugins: [
146
+ '@idealyst/config/plugin'
147
+ ]
126
148
  ```
127
149
 
128
150
  ## Inheritance Priority
@@ -130,9 +152,49 @@ idealyst-config generate --types-only --output src/env.d.ts
130
152
  Configs are merged in order, with later files overriding earlier ones:
131
153
 
132
154
  ```
133
- 1. --extends ../shared/.env (lowest priority)
134
- 2. --extends ../.env.common
135
- 3. --env .env (highest priority)
155
+ 1. extends[0]: ../shared/.env (lowest priority)
156
+ 2. extends[1]: ../common/.env
157
+ 3. envPath: .env (highest priority)
158
+ ```
159
+
160
+ ## Vite Setup
161
+
162
+ For Vite projects, add to `vite.config.ts`:
163
+
164
+ ```typescript
165
+ import { defineConfig } from 'vite'
166
+ import react from '@vitejs/plugin-react'
167
+
168
+ export default defineConfig({
169
+ plugins: [
170
+ react({
171
+ babel: {
172
+ plugins: [
173
+ ['@idealyst/config/plugin', {
174
+ extends: ['../shared/.env'],
175
+ envPath: '.env'
176
+ }]
177
+ ]
178
+ }
179
+ })
180
+ ]
181
+ })
182
+ ```
183
+
184
+ ## React Native / Metro Setup
185
+
186
+ Add to `babel.config.js`:
187
+
188
+ ```js
189
+ module.exports = {
190
+ presets: ['module:@react-native/babel-preset'],
191
+ plugins: [
192
+ ['@idealyst/config/plugin', {
193
+ extends: ['../shared/.env'],
194
+ envPath: '.env'
195
+ }]
196
+ ]
197
+ }
136
198
  ```
137
199
 
138
200
  ## API Reference
@@ -180,38 +242,17 @@ config.validate(['API_URL', 'AUTH_SECRET'])
180
242
  // Throws ConfigValidationError if any are missing
181
243
  ```
182
244
 
183
- ### `setConfig(config: Record<string, string>)`
184
-
185
- Initialize config values (call once at app startup).
186
-
187
- ```typescript
188
- import { setConfig } from '@idealyst/config'
189
- import { generatedConfig } from './config.generated'
190
-
191
- setConfig(generatedConfig)
192
- ```
193
-
194
- ## React Native Setup
195
-
196
- 1. Install react-native-config:
197
- ```bash
198
- npm install react-native-config
199
- cd ios && pod install
200
- ```
245
+ ## Type Generation (Optional)
201
246
 
202
- 2. Follow [react-native-config setup](https://github.com/luggit/react-native-config#setup)
247
+ For TypeScript autocomplete, generate type declarations:
203
248
 
204
- 3. Generate and use the same way as web:
205
249
  ```bash
206
- idealyst-config generate --extends ../shared/.env --env .env
250
+ npx idealyst-config generate --extends ../shared/.env --env .env --types-only
207
251
  ```
208
252
 
209
- ## Type Safety
210
-
211
- The CLI generates TypeScript declarations for autocomplete:
253
+ This creates `src/config.generated.d.ts`:
212
254
 
213
255
  ```typescript
214
- // Generated: src/config.generated.d.ts
215
256
  declare module '@idealyst/config' {
216
257
  interface ConfigKeys {
217
258
  API_URL: string
@@ -221,27 +262,47 @@ declare module '@idealyst/config' {
221
262
  }
222
263
  ```
223
264
 
224
- This provides autocomplete and catches typos at compile time.
225
-
226
- ## Build Integration
227
-
228
- Add to your build scripts:
265
+ Add to your build script for automatic updates:
229
266
 
230
267
  ```json
231
268
  {
232
269
  "scripts": {
233
- "prebuild": "idealyst-config generate --extends ../shared/.env",
234
- "build": "vite build"
270
+ "prebuild": "idealyst-config generate --extends ../shared/.env --types-only"
235
271
  }
236
272
  }
237
273
  ```
238
274
 
275
+ ## How It Works
276
+
277
+ The Babel plugin transforms your code at compile time:
278
+
279
+ **Input:**
280
+ ```typescript
281
+ import { config } from '@idealyst/config'
282
+
283
+ const apiUrl = config.get('API_URL')
284
+ ```
285
+
286
+ **Output (after Babel):**
287
+ ```typescript
288
+ import { config, setConfig as __idealyst_setConfig } from '@idealyst/config'
289
+ __idealyst_setConfig({ API_URL: "https://api.example.com", GOOGLE_CLIENT_ID: "abc123" })
290
+
291
+ const apiUrl = config.get('API_URL')
292
+ ```
293
+
294
+ This means:
295
+ - **No runtime .env parsing** - values are baked in at build time
296
+ - **Works with any bundler** - Vite, Webpack, Metro, esbuild
297
+ - **Tree-shakeable** - unused config keys can be eliminated
298
+ - **Secure** - .env files never shipped to client
299
+
239
300
  ## Best Practices
240
301
 
241
- 1. **Generate before build** - Add to prebuild script
242
- 2. **Gitignore generated files** - Add `config.generated.ts` to `.gitignore`
243
- 3. **Commit .env.example** - Document required keys without values
244
- 4. **Validate at startup** - Use `config.validate()` early
302
+ 1. **Gitignore .env files** - Never commit secrets
303
+ 2. **Commit .env.example** - Document required keys
304
+ 3. **Use shared config** - DRY principle for common values
305
+ 4. **Validate at startup** - Catch missing config early
245
306
 
246
307
  ## License
247
308
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/config",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
4
4
  "description": "Cross-platform configuration and environment variable support for React and React Native",
5
5
  "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/config#readme",
6
6
  "readme": "README.md",
@@ -35,6 +35,10 @@
35
35
  "require": "./src/index.ts"
36
36
  }
37
37
  },
38
+ "./plugin": {
39
+ "require": "./plugin.js",
40
+ "default": "./plugin.js"
41
+ },
38
42
  "./generate": {
39
43
  "types": "./src/cli/generate.ts",
40
44
  "import": "./src/cli/generate.ts",
@@ -61,6 +65,7 @@
61
65
  "files": [
62
66
  "src",
63
67
  "bin",
68
+ "plugin.js",
64
69
  "README.md"
65
70
  ],
66
71
  "keywords": [
package/plugin.js ADDED
@@ -0,0 +1,293 @@
1
+ /**
2
+ * @idealyst/config Babel plugin
3
+ *
4
+ * Injects config values at compile time from .env files.
5
+ *
6
+ * Usage in babel.config.js:
7
+ * ```js
8
+ * module.exports = {
9
+ * plugins: [
10
+ * ['@idealyst/config/plugin', {
11
+ * extends: ['../shared/.env'],
12
+ * envPath: '.env',
13
+ * required: ['API_URL', 'AUTH_SECRET'], // Warn if missing
14
+ * errorOnMissing: true // Fail build if missing
15
+ * }]
16
+ * ]
17
+ * }
18
+ * ```
19
+ */
20
+
21
+ const fs = require('fs')
22
+ const path = require('path')
23
+
24
+ // Track if we've already validated (to avoid duplicate warnings)
25
+ let hasValidated = false
26
+
27
+ /**
28
+ * Parse a .env file and extract key-value pairs.
29
+ */
30
+ function parseEnvFile(filePath) {
31
+ if (!fs.existsSync(filePath)) {
32
+ return {}
33
+ }
34
+
35
+ const content = fs.readFileSync(filePath, 'utf-8')
36
+ const config = {}
37
+
38
+ for (const line of content.split('\n')) {
39
+ const trimmed = line.trim()
40
+
41
+ if (!trimmed || trimmed.startsWith('#')) {
42
+ continue
43
+ }
44
+
45
+ const equalsIndex = trimmed.indexOf('=')
46
+ if (equalsIndex === -1) {
47
+ continue
48
+ }
49
+
50
+ let key = trimmed.substring(0, equalsIndex).trim()
51
+ let value = trimmed.substring(equalsIndex + 1).trim()
52
+
53
+ // Remove quotes if present
54
+ if ((value.startsWith('"') && value.endsWith('"')) ||
55
+ (value.startsWith("'") && value.endsWith("'"))) {
56
+ value = value.slice(1, -1)
57
+ }
58
+
59
+ // Strip VITE_ prefix to normalize
60
+ if (key.startsWith('VITE_')) {
61
+ key = key.substring(5)
62
+ }
63
+
64
+ config[key] = value
65
+ }
66
+
67
+ return config
68
+ }
69
+
70
+ /**
71
+ * Find .env file in directory.
72
+ */
73
+ function findEnvFile(directory) {
74
+ const candidates = ['.env.local', '.env.development', '.env']
75
+ for (const candidate of candidates) {
76
+ const envPath = path.join(directory, candidate)
77
+ if (fs.existsSync(envPath)) {
78
+ return envPath
79
+ }
80
+ }
81
+ return null
82
+ }
83
+
84
+ /**
85
+ * Look for shared .env in common monorepo locations.
86
+ */
87
+ function findSharedEnv(directory) {
88
+ const patterns = [
89
+ '../shared/.env',
90
+ '../../shared/.env',
91
+ '../../packages/shared/.env',
92
+ ]
93
+ for (const pattern of patterns) {
94
+ const sharedPath = path.resolve(directory, pattern)
95
+ if (fs.existsSync(sharedPath)) {
96
+ return sharedPath
97
+ }
98
+ }
99
+ return null
100
+ }
101
+
102
+ /**
103
+ * Load and merge config from .env files.
104
+ */
105
+ function loadConfig(options, projectRoot) {
106
+ const configs = []
107
+ const sources = []
108
+
109
+ // Load inherited configs first (lowest priority)
110
+ if (options.extends) {
111
+ for (const extendPath of options.extends) {
112
+ const resolvedPath = path.isAbsolute(extendPath)
113
+ ? extendPath
114
+ : path.resolve(projectRoot, extendPath)
115
+
116
+ if (fs.existsSync(resolvedPath)) {
117
+ configs.push(parseEnvFile(resolvedPath))
118
+ sources.push(resolvedPath)
119
+ }
120
+ }
121
+ } else {
122
+ // Auto-detect shared env
123
+ const sharedEnv = findSharedEnv(projectRoot)
124
+ if (sharedEnv) {
125
+ configs.push(parseEnvFile(sharedEnv))
126
+ sources.push(sharedEnv)
127
+ }
128
+ }
129
+
130
+ // Load main env file (highest priority)
131
+ let envPath = null
132
+ if (options.envPath) {
133
+ envPath = path.isAbsolute(options.envPath)
134
+ ? options.envPath
135
+ : path.resolve(projectRoot, options.envPath)
136
+ } else {
137
+ envPath = findEnvFile(projectRoot)
138
+ }
139
+
140
+ if (envPath && fs.existsSync(envPath)) {
141
+ configs.push(parseEnvFile(envPath))
142
+ sources.push(envPath)
143
+ }
144
+
145
+ // Merge configs (later configs override earlier ones)
146
+ return {
147
+ config: Object.assign({}, ...configs),
148
+ sources
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Validate required config keys and emit warnings/errors.
154
+ */
155
+ function validateConfig(configValues, options, projectRoot) {
156
+ if (hasValidated) return true
157
+ hasValidated = true
158
+
159
+ const required = options.required || []
160
+ if (required.length === 0) return true
161
+
162
+ const missing = required.filter(key => !(key in configValues))
163
+
164
+ if (missing.length === 0) return true
165
+
166
+ const errorOnMissing = options.errorOnMissing ?? false
167
+ const message = `@idealyst/config: Missing required config keys: ${missing.join(', ')}`
168
+
169
+ if (errorOnMissing) {
170
+ console.error('\n\x1b[31m✖ ' + message + '\x1b[0m')
171
+ console.error('\x1b[90m Add these keys to your .env file or check your plugin configuration.\x1b[0m\n')
172
+ throw new Error(message)
173
+ } else {
174
+ console.warn('\n\x1b[33m⚠ ' + message + '\x1b[0m')
175
+ console.warn('\x1b[90m Add these keys to your .env file to suppress this warning.\x1b[0m')
176
+ console.warn('\x1b[90m Set errorOnMissing: true to fail the build instead.\x1b[0m\n')
177
+ }
178
+
179
+ return false
180
+ }
181
+
182
+ /**
183
+ * Babel plugin that injects config values at compile time.
184
+ */
185
+ module.exports = function babelPluginIdealystConfig(babel) {
186
+ const { types: t } = babel
187
+
188
+ // Cache config per project root
189
+ const configCache = new Map()
190
+
191
+ // Reset validation state on new compilation
192
+ hasValidated = false
193
+
194
+ return {
195
+ name: '@idealyst/config',
196
+
197
+ visitor: {
198
+ Program: {
199
+ enter(programPath, state) {
200
+ const opts = state.opts || {}
201
+ const projectRoot = opts.root || state.cwd || process.cwd()
202
+
203
+ // Load config (cached per project root)
204
+ let configValues
205
+ if (!configCache.has(projectRoot)) {
206
+ const { config, sources } = loadConfig(opts, projectRoot)
207
+ configCache.set(projectRoot, config)
208
+ configValues = config
209
+
210
+ // Validate required keys (only once per build)
211
+ validateConfig(config, opts, projectRoot)
212
+
213
+ // Log loaded sources in verbose mode
214
+ if (opts.verbose && sources.length > 0) {
215
+ console.log('\x1b[36m@idealyst/config loaded:\x1b[0m')
216
+ sources.forEach(s => console.log(' ← ' + path.relative(projectRoot, s)))
217
+ console.log(' Keys:', Object.keys(config).join(', '))
218
+ }
219
+ } else {
220
+ configValues = configCache.get(projectRoot)
221
+ }
222
+
223
+ // Track if this file imports from @idealyst/config
224
+ let hasConfigImport = false
225
+ let configImportPath = null
226
+
227
+ // Find imports from @idealyst/config
228
+ programPath.traverse({
229
+ ImportDeclaration(importPath) {
230
+ if (importPath.node.source.value === '@idealyst/config') {
231
+ hasConfigImport = true
232
+ configImportPath = importPath
233
+ }
234
+ }
235
+ })
236
+
237
+ if (!hasConfigImport || !configImportPath) {
238
+ return
239
+ }
240
+
241
+ // Check if setConfig is already imported
242
+ let hasSetConfigImport = false
243
+ let setConfigLocalName = '__idealyst_setConfig'
244
+
245
+ for (const specifier of configImportPath.node.specifiers) {
246
+ if (t.isImportSpecifier(specifier) &&
247
+ t.isIdentifier(specifier.imported) &&
248
+ specifier.imported.name === 'setConfig') {
249
+ hasSetConfigImport = true
250
+ setConfigLocalName = specifier.local.name
251
+ break
252
+ }
253
+ }
254
+
255
+ // Add setConfig to imports if not already present
256
+ if (!hasSetConfigImport) {
257
+ configImportPath.node.specifiers.push(
258
+ t.importSpecifier(
259
+ t.identifier('__idealyst_setConfig'),
260
+ t.identifier('setConfig')
261
+ )
262
+ )
263
+ }
264
+
265
+ // Create the config object literal
266
+ const configProperties = Object.entries(configValues).map(([key, value]) =>
267
+ t.objectProperty(t.identifier(key), t.stringLiteral(value))
268
+ )
269
+ const configObject = t.objectExpression(configProperties)
270
+
271
+ // Create setConfig call
272
+ const setConfigCall = t.expressionStatement(
273
+ t.callExpression(
274
+ t.identifier(setConfigLocalName),
275
+ [configObject]
276
+ )
277
+ )
278
+
279
+ // Insert after the import statement
280
+ const importIndex = programPath.node.body.indexOf(configImportPath.node)
281
+ programPath.node.body.splice(importIndex + 1, 0, setConfigCall)
282
+ }
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ // Export utilities for direct use
289
+ module.exports.parseEnvFile = parseEnvFile
290
+ module.exports.loadConfig = loadConfig
291
+ module.exports.findEnvFile = findEnvFile
292
+ module.exports.findSharedEnv = findSharedEnv
293
+ module.exports.validateConfig = validateConfig
@@ -1,32 +1,63 @@
1
1
  import type { IConfig } from './types'
2
2
  import { ConfigValidationError } from './types'
3
3
 
4
- // react-native-config provides a Config object with all env variables
5
- // eslint-disable-next-line @typescript-eslint/no-var-requires
6
- let RNConfig: Record<string, string | undefined> = {}
7
-
8
- try {
9
- // Dynamic import to handle cases where react-native-config is not installed
10
- // This allows the package to be used in web-only projects without errors
11
- RNConfig = require('react-native-config').default || require('react-native-config')
12
- } catch {
13
- // react-native-config not available - will be empty object
14
- // This is expected in web environments or when the native module isn't linked
4
+ /**
5
+ * Config store - initialized from react-native-config, can be overridden via setConfig().
6
+ */
7
+ let configStore: Record<string, string> = {}
8
+
9
+ /**
10
+ * Initialize from react-native-config if available.
11
+ */
12
+ function initFromRNConfig(): void {
13
+ try {
14
+ // Dynamic import to handle cases where react-native-config is not installed
15
+ const RNConfig = require('react-native-config').default || require('react-native-config')
16
+ if (RNConfig && typeof RNConfig === 'object') {
17
+ configStore = { ...RNConfig }
18
+ }
19
+ } catch {
20
+ // react-native-config not available - configStore remains empty
21
+ // Values will be injected by Babel plugin via setConfig()
22
+ }
15
23
  }
16
24
 
25
+ // Initialize on module load
26
+ initFromRNConfig()
27
+
17
28
  /**
18
- * Native implementation of IConfig using react-native-config.
19
- *
20
- * This implementation provides direct access to .env variables without
21
- * any prefix transformation, as react-native-config doesn't require prefixes.
29
+ * Set config values. Called by Babel plugin at compile time,
30
+ * or can be called manually.
31
+ */
32
+ export function setConfig(config: Record<string, string>): void {
33
+ configStore = { ...configStore, ...config }
34
+ }
35
+
36
+ /**
37
+ * Clear all config values. Useful for testing.
38
+ */
39
+ export function clearConfig(): void {
40
+ configStore = {}
41
+ }
42
+
43
+ /**
44
+ * Get the raw config store. Useful for debugging.
45
+ */
46
+ export function getConfigStore(): Record<string, string> {
47
+ return { ...configStore }
48
+ }
49
+
50
+ /**
51
+ * Native implementation of IConfig.
22
52
  *
23
- * The .env file should use canonical names:
24
- * API_URL=https://api.example.com
25
- * GOOGLE_CLIENT_ID=abc123
53
+ * Config values come from:
54
+ * 1. react-native-config (if installed)
55
+ * 2. Babel plugin injection via setConfig()
56
+ * 3. Manual setConfig() calls
26
57
  */
27
58
  class NativeConfig implements IConfig {
28
59
  get(key: string, defaultValue?: string): string | undefined {
29
- return RNConfig[key] ?? defaultValue
60
+ return configStore[key] ?? defaultValue
30
61
  }
31
62
 
32
63
  getRequired(key: string): string {
@@ -41,11 +72,11 @@ class NativeConfig implements IConfig {
41
72
  }
42
73
 
43
74
  has(key: string): boolean {
44
- return RNConfig[key] !== undefined
75
+ return configStore[key] !== undefined
45
76
  }
46
77
 
47
78
  keys(): string[] {
48
- return Object.keys(RNConfig)
79
+ return Object.keys(configStore).sort()
49
80
  }
50
81
 
51
82
  validate(requiredKeys: string[]): void {
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Native entry point for @idealyst/config
3
3
  *
4
- * Uses react-native-config for environment variable access.
5
- * No prefix is required - use canonical names directly.
4
+ * Config values come from:
5
+ * 1. react-native-config (if installed)
6
+ * 2. Babel plugin injection via setConfig()
6
7
  *
7
8
  * @example
8
9
  * ```typescript
@@ -13,11 +14,11 @@
13
14
  * ```
14
15
  */
15
16
 
16
- import NativeConfig from './config.native'
17
+ import NativeConfig, { setConfig, clearConfig, getConfigStore } from './config.native'
17
18
 
18
19
  // Create singleton instance for native
19
20
  const config = new NativeConfig()
20
21
 
21
22
  export default config
22
- export { config, config as Config, NativeConfig }
23
+ export { config, config as Config, NativeConfig, setConfig, clearConfig, getConfigStore }
23
24
  export * from './types'