@@ -12,67 +12,22 @@ const NumberIsFinite = Number.isFinite
1212const NumberParseInt = Number . parseInt
1313const StringCtor = String
1414
15- /**
16- * Convert an environment variable value to a boolean.
17- */
18- /*@__NO_SIDE_EFFECTS__ */
19- export function envAsBoolean ( value : unknown , defaultValue = false ) : boolean {
20- if ( typeof value === 'string' ) {
21- const trimmed = value . trim ( )
22- return trimmed === '1' || trimmed . toLowerCase ( ) === 'true'
23- }
24- if ( value === null || value === undefined ) {
25- return ! ! defaultValue
26- }
27- return ! ! value
28- }
29-
30- /**
31- * Convert an environment variable value to a number.
32- */
33- /*@__NO_SIDE_EFFECTS__ */
34- export function envAsNumber ( value : unknown , defaultValue = 0 ) : number {
35- const numOrNaN = NumberParseInt ( String ( value ) , 10 )
36- const numMayBeNegZero = NumberIsFinite ( numOrNaN )
37- ? numOrNaN
38- : NumberCtor ( defaultValue )
39- // Ensure -0 is treated as 0.
40- return numMayBeNegZero || 0
41- }
42-
43- /**
44- * Convert an environment variable value to a trimmed string.
45- */
46- /*@__NO_SIDE_EFFECTS__ */
47- export function envAsString ( value : unknown , defaultValue = '' ) : string {
48- if ( typeof value === 'string' ) {
49- return value . trim ( )
50- }
51- if ( value === null || value === undefined ) {
52- return defaultValue === '' ? defaultValue : StringCtor ( defaultValue ) . trim ( )
53- }
54- return StringCtor ( value ) . trim ( )
55- }
56-
57- /**
58- * Helper to find a case-insensitive key match in an object.
59- * Uses fast path checks to minimize expensive toUpperCase() calls.
60- */
61- function findCaseInsensitiveKey (
62- obj : Record < string , string | undefined > ,
63- upperProp : string ,
64- ) : string | undefined {
65- const targetLength = upperProp . length
66- for ( const key of Object . keys ( obj ) ) {
67- // Fast path: bail early if lengths don't match.
68- if ( key . length !== targetLength ) continue
69- // Only call toUpperCase if length matches.
70- if ( key . toUpperCase ( ) === upperProp ) {
71- return key
72- }
73- }
74- return undefined
75- }
15+ // Common environment variables that have case sensitivity issues on Windows.
16+ // These are checked with case-insensitive matching when exact matches fail.
17+ const caseInsensitiveKeys = new Set ( [
18+ 'APPDATA' ,
19+ 'COMSPEC' ,
20+ 'HOME' ,
21+ 'LOCALAPPDATA' ,
22+ 'PATH' ,
23+ 'PATHEXT' ,
24+ 'PROGRAMFILES' ,
25+ 'SYSTEMROOT' ,
26+ 'TEMP' ,
27+ 'TMP' ,
28+ 'USERPROFILE' ,
29+ 'WINDIR' ,
30+ ] )
7631
7732/**
7833 * Create a case-insensitive environment variable Proxy for Windows compatibility.
@@ -112,22 +67,6 @@ export function createEnvProxy(
11267 base : NodeJS . ProcessEnv ,
11368 overrides ?: Record < string , string | undefined > ,
11469) : NodeJS . ProcessEnv {
115- // Common environment variables that have case sensitivity issues on Windows.
116- // These are checked with case-insensitive matching when exact matches fail.
117- const caseInsensitiveKeys = new Set ( [
118- 'PATH' ,
119- 'TEMP' ,
120- 'TMP' ,
121- 'HOME' ,
122- 'USERPROFILE' ,
123- 'APPDATA' ,
124- 'LOCALAPPDATA' ,
125- 'PROGRAMFILES' ,
126- 'SYSTEMROOT' ,
127- 'WINDIR' ,
128- 'COMSPEC' ,
129- 'PATHEXT' ,
130- ] )
13170
13271 return new Proxy (
13372 { } ,
@@ -152,13 +91,13 @@ export function createEnvProxy(
15291 if ( caseInsensitiveKeys . has ( upperProp ) ) {
15392 // Check overrides with case variations.
15493 if ( overrides ) {
155- const key = findCaseInsensitiveKey ( overrides , upperProp )
94+ const key = findCaseInsensitiveEnvKey ( overrides , upperProp )
15695 if ( key !== undefined ) {
15796 return overrides [ key ]
15897 }
15998 }
16099 // Check base with case variations.
161- const key = findCaseInsensitiveKey ( base , upperProp )
100+ const key = findCaseInsensitiveEnvKey ( base , upperProp )
162101 if ( key !== undefined ) {
163102 return base [ key ]
164103 }
@@ -210,10 +149,10 @@ export function createEnvProxy(
210149 // Case-insensitive check.
211150 const upperProp = prop . toUpperCase ( )
212151 if ( caseInsensitiveKeys . has ( upperProp ) ) {
213- if ( overrides && findCaseInsensitiveKey ( overrides , upperProp ) !== undefined ) {
152+ if ( overrides && findCaseInsensitiveEnvKey ( overrides , upperProp ) !== undefined ) {
214153 return true
215154 }
216- if ( findCaseInsensitiveKey ( base , upperProp ) !== undefined ) {
155+ if ( findCaseInsensitiveEnvKey ( base , upperProp ) !== undefined ) {
217156 return true
218157 }
219158 }
@@ -231,3 +170,92 @@ export function createEnvProxy(
231170 } ,
232171 ) as NodeJS . ProcessEnv
233172}
173+
174+ /**
175+ * Convert an environment variable value to a boolean.
176+ */
177+ /*@__NO_SIDE_EFFECTS__ */
178+ export function envAsBoolean ( value : unknown , defaultValue = false ) : boolean {
179+ if ( typeof value === 'string' ) {
180+ const trimmed = value . trim ( )
181+ return trimmed === '1' || trimmed . toLowerCase ( ) === 'true'
182+ }
183+ if ( value === null || value === undefined ) {
184+ return ! ! defaultValue
185+ }
186+ return ! ! value
187+ }
188+
189+ /**
190+ * Convert an environment variable value to a number.
191+ */
192+ /*@__NO_SIDE_EFFECTS__ */
193+ export function envAsNumber ( value : unknown , defaultValue = 0 ) : number {
194+ const numOrNaN = NumberParseInt ( String ( value ) , 10 )
195+ const numMayBeNegZero = NumberIsFinite ( numOrNaN )
196+ ? numOrNaN
197+ : NumberCtor ( defaultValue )
198+ // Ensure -0 is treated as 0.
199+ return numMayBeNegZero || 0
200+ }
201+
202+ /**
203+ * Convert an environment variable value to a trimmed string.
204+ */
205+ /*@__NO_SIDE_EFFECTS__ */
206+ export function envAsString ( value : unknown , defaultValue = '' ) : string {
207+ if ( typeof value === 'string' ) {
208+ return value . trim ( )
209+ }
210+ if ( value === null || value === undefined ) {
211+ return defaultValue === '' ? defaultValue : StringCtor ( defaultValue ) . trim ( )
212+ }
213+ return StringCtor ( value ) . trim ( )
214+ }
215+
216+ /**
217+ * Find a case-insensitive environment variable key match.
218+ * Searches for an environment variable key that matches the given uppercase name,
219+ * using optimized fast-path checks to minimize expensive toUpperCase() calls.
220+ *
221+ * **Use Cases:**
222+ * - Finding PATH when env object has "Path" or "path"
223+ * - Cross-platform env var access where case may vary
224+ * - Custom case-insensitive env lookups
225+ *
226+ * **Performance:**
227+ * - Fast path: Checks length first (O(1)) before toUpperCase (expensive)
228+ * - Only converts to uppercase when length matches
229+ * - Early exit on first match
230+ *
231+ * @param env - Environment object or env-like record to search
232+ * @param upperEnvVarName - Uppercase environment variable name to find (e.g., 'PATH')
233+ * @returns The actual key from env that matches (e.g., 'Path'), or undefined
234+ *
235+ * @example
236+ * // Find PATH regardless of case
237+ * const envObj = { Path: 'C:\\Windows', NODE_ENV: 'test' }
238+ * const key = findCaseInsensitiveEnvKey(envObj, 'PATH')
239+ * console.log(key) // 'Path'
240+ * console.log(envObj[key]) // 'C:\\Windows'
241+ *
242+ * @example
243+ * // Not found returns undefined
244+ * const key = findCaseInsensitiveEnvKey({}, 'MISSING')
245+ * console.log(key) // undefined
246+ */
247+ export function findCaseInsensitiveEnvKey (
248+ env : Record < string , string | undefined > ,
249+ upperEnvVarName : string ,
250+ ) : string | undefined {
251+ const targetLength = upperEnvVarName . length
252+ for ( const key of Object . keys ( env ) ) {
253+ // Fast path: bail early if lengths don't match.
254+ if ( key . length !== targetLength ) continue
255+ // Only call toUpperCase if length matches.
256+ if ( key . toUpperCase ( ) === upperEnvVarName ) {
257+ return key
258+ }
259+ }
260+ return undefined
261+ }
0 commit comments