@@ -14,15 +14,15 @@ export async function getEnvFileVars(envFilePath) {
14
14
}
15
15
// Get the file extension
16
16
const ext = extname ( absolutePath ) . toLowerCase ( ) ;
17
- let env = { } ;
17
+ let env ;
18
18
if ( IMPORT_HOOK_EXTENSIONS . includes ( ext ) ) {
19
19
// For some reason in ES Modules, only JSON file types need to be specifically delinated when importing them
20
20
let attributeTypes = { } ;
21
21
if ( ext === '.json' ) {
22
22
attributeTypes = { [ importAttributesKeyword ] : { type : 'json' } } ;
23
23
}
24
24
const res = await import ( pathToFileURL ( absolutePath ) . href , attributeTypes ) ;
25
- if ( 'default' in res ) {
25
+ if ( typeof res === 'object' && res && 'default' in res ) {
26
26
env = res . default ;
27
27
}
28
28
else {
@@ -32,12 +32,14 @@ export async function getEnvFileVars(envFilePath) {
32
32
if ( isPromise ( env ) ) {
33
33
env = await env ;
34
34
}
35
+ return normalizeEnvObject ( env , absolutePath ) ;
35
36
}
36
- else {
37
- const file = readFileSync ( absolutePath , { encoding : 'utf8' } ) ;
38
- env = parseEnvString ( file ) ;
37
+ const file = readFileSync ( absolutePath , { encoding : 'utf8' } ) ;
38
+ switch ( ext ) {
39
+ // other loaders can be added here
40
+ default :
41
+ return parseEnvString ( file ) ;
39
42
}
40
- return env ;
41
43
}
42
44
/**
43
45
* Parse out all env vars from a given env file string and return an object
@@ -61,23 +63,18 @@ export function parseEnvVars(envString) {
61
63
// Note: match[1] is the full env=var line
62
64
const key = match [ 2 ] . trim ( ) ;
63
65
let value = match [ 3 ] . trim ( ) ;
64
- // remove any surrounding quotes
65
- value = value
66
- . replace ( / ( ^ [ ' " ] | [ ' " ] $ ) / g, '' )
67
- . replace ( / \\ n / g, '\n' ) ;
68
- // Convert string to JS type if appropriate
69
- if ( value !== '' && ! isNaN ( + value ) ) {
70
- matches [ key ] = + value ;
71
- }
72
- else if ( value === 'true' ) {
73
- matches [ key ] = true ;
74
- }
75
- else if ( value === 'false' ) {
76
- matches [ key ] = false ;
66
+ // if the string is quoted, remove everything after the final
67
+ // quote. This implicitly removes inline comments.
68
+ if ( value . startsWith ( "'" ) || value . startsWith ( '"' ) ) {
69
+ value = value . slice ( 1 , value . lastIndexOf ( value [ 0 ] ) ) ;
77
70
}
78
71
else {
79
- matches [ key ] = value ;
72
+ // if the string is not quoted, we need to explicitly remove
73
+ // inline comments.
74
+ value = value . split ( '#' ) [ 0 ] . trim ( ) ;
80
75
}
76
+ value = value . replace ( / \\ n / g, '\n' ) ;
77
+ matches [ key ] = value ;
81
78
}
82
79
return JSON . parse ( JSON . stringify ( matches ) ) ;
83
80
}
@@ -95,3 +92,25 @@ export function stripEmptyLines(envString) {
95
92
const emptyLinesRegex = / ( ^ \n ) / gim;
96
93
return envString . replace ( emptyLinesRegex , '' ) ;
97
94
}
95
+ /**
96
+ * If we load data from a file like .js, the user
97
+ * might export something which is not an object.
98
+ *
99
+ * This function ensures that the input is valid,
100
+ * and converts the object's values to strings, for
101
+ * consistincy. See issue #125 for details.
102
+ */
103
+ export function normalizeEnvObject ( input , absolutePath ) {
104
+ if ( typeof input !== 'object' || ! input ) {
105
+ throw new Error ( `env-cmd cannot load “${ absolutePath } ” because it does not export an object.` ) ;
106
+ }
107
+ const env = { } ;
108
+ for ( const [ key , value ] of Object . entries ( input ) ) {
109
+ // we're intentionally stringifying the value here, to
110
+ // match what `child_process.spawn` does when loading
111
+ // env variables.
112
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
113
+ env [ key ] = `${ value } ` ;
114
+ }
115
+ return env ;
116
+ }
0 commit comments