1
1
import { join , dirname , resolve , isAbsolute } from 'path' ;
2
- import { forKey , validateOptions } from '@app-config/extension-utils' ;
2
+ import {
3
+ forKey ,
4
+ validateOptions ,
5
+ validationFunction ,
6
+ ValidationFunction ,
7
+ } from '@app-config/extension-utils' ;
3
8
import {
4
9
ParsedValue ,
5
10
ParsedValueMetadata ,
6
11
ParsingExtension ,
7
12
AppConfigError ,
8
13
NotFoundError ,
9
14
FailedToSelectSubObject ,
15
+ InObject ,
10
16
} from '@app-config/core' ;
11
17
import {
12
18
currentEnvironment ,
@@ -46,26 +52,27 @@ export function extendsDirective(): ParsingExtension {
46
52
47
53
/** Lookup a property in the same file, and "copy" it */
48
54
export function extendsSelfDirective ( ) : ParsingExtension {
49
- return forKey (
50
- '$extendsSelf' ,
51
- validateOptions (
52
- ( SchemaBuilder ) => SchemaBuilder . stringSchema ( ) ,
53
- ( value ) => async ( parse , _ , __ , ___ , root ) => {
54
- // we temporarily use a ParsedValue literal so that we get the same property lookup semantics
55
- const selected = ParsedValue . literal ( root ) . property ( value . split ( '.' ) ) ;
55
+ const validate : ValidationFunction < string > = validationFunction ( ( { stringSchema } ) =>
56
+ stringSchema ( ) ,
57
+ ) ;
56
58
57
- if ( selected === undefined ) {
58
- throw new AppConfigError ( `$extendsSelf selector was not found ( ${ value } )` ) ;
59
- }
59
+ return forKey ( '$extendsSelf' , ( input , key , ctx ) => async ( parse , _ , __ , ___ , root ) => {
60
+ const value = ( await parse ( input ) ) . toJSON ( ) ;
61
+ validate ( value , [ ... ctx , key ] ) ;
60
62
61
- if ( selected . asObject ( ) !== undefined ) {
62
- return parse ( selected . toJSON ( ) , { shouldMerge : true } ) ;
63
- }
63
+ // we temporarily use a ParsedValue literal so that we get the same property lookup semantics
64
+ const selected = ParsedValue . literal ( root ) . property ( value . split ( '.' ) ) ;
64
65
65
- return parse ( selected . toJSON ( ) , { shouldFlatten : true } ) ;
66
- } ,
67
- ) ,
68
- ) ;
66
+ if ( selected === undefined ) {
67
+ throw new AppConfigError ( `$extendsSelf selector was not found (${ value } )` ) ;
68
+ }
69
+
70
+ if ( selected . asObject ( ) !== undefined ) {
71
+ return parse ( selected . toJSON ( ) , { shouldMerge : true } ) ;
72
+ }
73
+
74
+ return parse ( selected . toJSON ( ) , { shouldFlatten : true } ) ;
75
+ } ) ;
69
76
}
70
77
71
78
/** Looks up an environment-specific value ($env) */
@@ -108,6 +115,7 @@ export function envDirective(
108
115
`An $env directive was used, but none matched the current environment (wanted ${ environment } , saw [${ found } ])` ,
109
116
) ;
110
117
} ,
118
+ // $env is lazy so that non-applicable envs don't get evaluated
111
119
{ lazy : true } ,
112
120
) ,
113
121
) ;
@@ -158,36 +166,44 @@ export function environmentVariableSubstitution(
158
166
) : ParsingExtension {
159
167
const envType = environmentOverride ?? currentEnvironment ( aliases , environmentSourceNames ) ;
160
168
161
- return forKey (
162
- [ '$substitute' , '$subs' ] ,
163
- validateOptions (
164
- ( SchemaBuilder ) =>
165
- SchemaBuilder . oneOf (
166
- SchemaBuilder . stringSchema ( ) ,
167
- SchemaBuilder . emptySchema ( ) . addString ( '$name' ) . addString ( '$fallback' , { } , false ) ,
168
- ) ,
169
- ( value ) => ( parse ) => {
170
- if ( typeof value === 'object' ) {
171
- const { $name : variableName , $fallback : fallback } = value ;
172
- const resolvedValue = process . env [ variableName ] ;
169
+ const validateObject : ValidationFunction <
170
+ Record < string , any >
171
+ > = validationFunction ( ( { emptySchema } ) => emptySchema ( ) . addAdditionalProperties ( ) ) ;
173
172
174
- if ( fallback !== undefined ) {
175
- return parse ( resolvedValue || fallback , { shouldFlatten : true } ) ;
176
- }
173
+ const validateString : ValidationFunction < string > = validationFunction ( ( { stringSchema } ) =>
174
+ stringSchema ( ) ,
175
+ ) ;
177
176
178
- if ( ! resolvedValue ) {
179
- throw new AppConfigError (
180
- `$substitute could not find ${ variableName } environment variable` ,
181
- ) ;
182
- }
177
+ return forKey ( [ '$substitute' , '$subs' ] , ( value , key , ctx ) => async ( parse ) => {
178
+ if ( typeof value === 'string' ) {
179
+ return parse ( performAllSubstitutions ( value , envType ) , { shouldFlatten : true } ) ;
180
+ }
183
181
184
- return parse ( resolvedValue , { shouldFlatten : true } ) ;
185
- }
182
+ validateObject ( value , [ ... ctx , key ] ) ;
183
+ if ( Array . isArray ( value ) ) throw new AppConfigError ( '$substitute was given an array' ) ;
186
184
187
- return parse ( performAllSubstitutions ( value , envType ) , { shouldFlatten : true } ) ;
188
- } ,
189
- ) ,
190
- ) ;
185
+ const { $name : variableName , $fallback : fallback } = value ;
186
+ validateString ( variableName , [ ...ctx , key , [ InObject , '$name' ] ] ) ;
187
+
188
+ const resolvedValue = process . env [ variableName ] ;
189
+
190
+ if ( resolvedValue ) {
191
+ return parse ( resolvedValue , { shouldFlatten : true } ) ;
192
+ }
193
+
194
+ if ( fallback !== undefined ) {
195
+ const fallbackValue = ( await parse ( fallback ) ) . toJSON ( ) ;
196
+ validateString ( fallbackValue , [ ...ctx , key , [ InObject , '$fallback' ] ] ) ;
197
+
198
+ return parse ( fallbackValue , { shouldFlatten : true } ) ;
199
+ }
200
+
201
+ if ( ! resolvedValue ) {
202
+ throw new AppConfigError ( `$substitute could not find ${ variableName } environment variable` ) ;
203
+ }
204
+
205
+ return parse ( resolvedValue , { shouldFlatten : true } ) ;
206
+ } ) ;
191
207
}
192
208
193
209
// common logic for $extends and $override
@@ -206,7 +222,7 @@ function fileReferenceDirective(keyName: string, meta: ParsedValueMetadata): Par
206
222
207
223
return SchemaBuilder . oneOf ( reference , SchemaBuilder . arraySchema ( reference ) ) ;
208
224
} ,
209
- ( value ) => async ( parse , _ , context , extensions ) => {
225
+ ( value ) => async ( _ , __ , context , extensions ) => {
210
226
const retrieveFile = async ( filepath : string , subselector ?: string , isOptional = false ) => {
211
227
let resolvedPath = filepath ;
212
228
0 commit comments