@idealyst/config 1.2.14 → 1.2.16
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 +38 -0
- package/package.json +1 -1
- package/plugin.js +64 -4
- package/src/config.native.ts +52 -21
- package/src/index.native.ts +5 -4
package/README.md
CHANGED
|
@@ -91,11 +91,49 @@ That's it! The Babel plugin reads your .env files at compile time and injects th
|
|
|
91
91
|
// Main .env file (highest priority, default: auto-detect)
|
|
92
92
|
envPath: '.env',
|
|
93
93
|
|
|
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
|
+
|
|
94
103
|
// Project root (default: process.cwd())
|
|
95
104
|
root: '/path/to/project'
|
|
96
105
|
}]
|
|
97
106
|
```
|
|
98
107
|
|
|
108
|
+
### Build-time Validation
|
|
109
|
+
|
|
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
|
+
```
|
|
121
|
+
|
|
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.
|
|
127
|
+
```
|
|
128
|
+
|
|
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.
|
|
133
|
+
|
|
134
|
+
Error: @idealyst/config: Missing required config keys: JWT_SECRET
|
|
135
|
+
```
|
|
136
|
+
|
|
99
137
|
### Auto-detection
|
|
100
138
|
|
|
101
139
|
If you don't specify options, the plugin will:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/config",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.16",
|
|
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",
|
package/plugin.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* plugins: [
|
|
10
10
|
* ['@idealyst/config/plugin', {
|
|
11
11
|
* extends: ['../shared/.env'],
|
|
12
|
-
* envPath: '.env'
|
|
12
|
+
* envPath: '.env',
|
|
13
|
+
* required: ['API_URL', 'AUTH_SECRET'], // Warn if missing
|
|
14
|
+
* errorOnMissing: true // Fail build if missing
|
|
13
15
|
* }]
|
|
14
16
|
* ]
|
|
15
17
|
* }
|
|
@@ -19,6 +21,9 @@
|
|
|
19
21
|
const fs = require('fs')
|
|
20
22
|
const path = require('path')
|
|
21
23
|
|
|
24
|
+
// Track if we've already validated (to avoid duplicate warnings)
|
|
25
|
+
let hasValidated = false
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Parse a .env file and extract key-value pairs.
|
|
24
29
|
*/
|
|
@@ -99,6 +104,7 @@ function findSharedEnv(directory) {
|
|
|
99
104
|
*/
|
|
100
105
|
function loadConfig(options, projectRoot) {
|
|
101
106
|
const configs = []
|
|
107
|
+
const sources = []
|
|
102
108
|
|
|
103
109
|
// Load inherited configs first (lowest priority)
|
|
104
110
|
if (options.extends) {
|
|
@@ -109,6 +115,7 @@ function loadConfig(options, projectRoot) {
|
|
|
109
115
|
|
|
110
116
|
if (fs.existsSync(resolvedPath)) {
|
|
111
117
|
configs.push(parseEnvFile(resolvedPath))
|
|
118
|
+
sources.push(resolvedPath)
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
} else {
|
|
@@ -116,6 +123,7 @@ function loadConfig(options, projectRoot) {
|
|
|
116
123
|
const sharedEnv = findSharedEnv(projectRoot)
|
|
117
124
|
if (sharedEnv) {
|
|
118
125
|
configs.push(parseEnvFile(sharedEnv))
|
|
126
|
+
sources.push(sharedEnv)
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -131,10 +139,44 @@ function loadConfig(options, projectRoot) {
|
|
|
131
139
|
|
|
132
140
|
if (envPath && fs.existsSync(envPath)) {
|
|
133
141
|
configs.push(parseEnvFile(envPath))
|
|
142
|
+
sources.push(envPath)
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// Merge configs (later configs override earlier ones)
|
|
137
|
-
return
|
|
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
|
|
138
180
|
}
|
|
139
181
|
|
|
140
182
|
/**
|
|
@@ -146,6 +188,9 @@ module.exports = function babelPluginIdealystConfig(babel) {
|
|
|
146
188
|
// Cache config per project root
|
|
147
189
|
const configCache = new Map()
|
|
148
190
|
|
|
191
|
+
// Reset validation state on new compilation
|
|
192
|
+
hasValidated = false
|
|
193
|
+
|
|
149
194
|
return {
|
|
150
195
|
name: '@idealyst/config',
|
|
151
196
|
|
|
@@ -156,10 +201,24 @@ module.exports = function babelPluginIdealystConfig(babel) {
|
|
|
156
201
|
const projectRoot = opts.root || state.cwd || process.cwd()
|
|
157
202
|
|
|
158
203
|
// Load config (cached per project root)
|
|
204
|
+
let configValues
|
|
159
205
|
if (!configCache.has(projectRoot)) {
|
|
160
|
-
|
|
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)
|
|
161
221
|
}
|
|
162
|
-
const configValues = configCache.get(projectRoot)
|
|
163
222
|
|
|
164
223
|
// Track if this file imports from @idealyst/config
|
|
165
224
|
let hasConfigImport = false
|
|
@@ -231,3 +290,4 @@ module.exports.parseEnvFile = parseEnvFile
|
|
|
231
290
|
module.exports.loadConfig = loadConfig
|
|
232
291
|
module.exports.findEnvFile = findEnvFile
|
|
233
292
|
module.exports.findSharedEnv = findSharedEnv
|
|
293
|
+
module.exports.validateConfig = validateConfig
|
package/src/config.native.ts
CHANGED
|
@@ -1,32 +1,63 @@
|
|
|
1
1
|
import type { IConfig } from './types'
|
|
2
2
|
import { ConfigValidationError } from './types'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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
|
|
75
|
+
return configStore[key] !== undefined
|
|
45
76
|
}
|
|
46
77
|
|
|
47
78
|
keys(): string[] {
|
|
48
|
-
return Object.keys(
|
|
79
|
+
return Object.keys(configStore).sort()
|
|
49
80
|
}
|
|
50
81
|
|
|
51
82
|
validate(requiredKeys: string[]): void {
|
package/src/index.native.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Native entry point for @idealyst/config
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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'
|