-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathindex.js
More file actions
159 lines (135 loc) · 3.96 KB
/
index.js
File metadata and controls
159 lines (135 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
'use strict'
const Ajv = require('ajv')
const { parseEnv } = require('node:util')
const { readFileSync } = require('node:fs')
const { resolve } = require('node:path')
const separator = {
keyword: 'separator',
type: 'string',
metaSchema: {
type: 'string',
description: 'value separator'
},
modifying: true,
valid: true,
errors: false,
compile: (schema) => (data, { parentData: pData, parentDataProperty: pDataProperty }) => {
pData[pDataProperty] = data === '' ? [] : data.split(schema)
}
}
function expandVariables (obj) {
// Expand environment variables in the format $VAR or ${VAR}
for (const key in obj) {
const value = obj[key]
if (typeof value === 'string') {
obj[key] = value.replace(/\$\{?([A-Z_][A-Z0-9_]*)\}?/gi, (match, varName) => {
return obj[varName] !== undefined ? obj[varName] : match
})
}
}
}
const optsSchema = {
type: 'object',
required: ['schema'],
properties: {
schema: { type: 'object', additionalProperties: true },
data: {
oneOf: [
{ type: 'array', items: { type: 'object' }, minItems: 1 },
{ type: 'object' }
],
default: {}
},
env: { type: 'boolean', default: true },
dotenv: { type: ['boolean', 'object'], default: false },
expandEnv: { type: ['boolean'], default: false },
ajv: { type: 'object', additionalProperties: true }
}
}
const sharedAjvInstance = getDefaultInstance()
const optsSchemaValidator = sharedAjvInstance.compile(optsSchema)
function envSchema (_opts) {
const opts = Object.assign({}, _opts)
if (opts.schema?.[Symbol.for('fluent-schema-object')]) {
opts.schema = opts.schema.valueOf()
}
const isOptionValid = optsSchemaValidator(opts)
if (!isOptionValid) {
const error = new Error(sharedAjvInstance.errorsText(optsSchemaValidator.errors, { dataVar: 'opts' }))
error.errors = optsSchemaValidator.errors
throw error
}
const { schema } = opts
schema.additionalProperties = false
let { data, dotenv, env, expandEnv } = opts
if (!Array.isArray(data)) {
data = [data]
}
let parsedEnv
if (dotenv) {
const dotenvOpts = typeof dotenv === 'object' ? dotenv : {}
const path = dotenvOpts.path || '.env'
const encoding = dotenvOpts.encoding || 'utf8'
try {
const envFileContent = readFileSync(resolve(path), encoding)
parsedEnv = parseEnv(envFileContent)
} catch (err) {
// Silently ignore if file doesn't exist
if (err.code !== 'ENOENT') {
throw err
}
parsedEnv = {}
}
}
/* istanbul ignore else */
if (env) {
data.unshift(process.env)
}
if (parsedEnv) {
data.unshift(parsedEnv)
}
const merge = {}
data.forEach(d => Object.assign(merge, d))
if (expandEnv) {
expandVariables(merge)
}
const ajv = chooseAjvInstance(sharedAjvInstance, opts.ajv)
const valid = ajv.validate(schema, merge)
if (!valid) {
const error = new Error(ajv.errorsText(ajv.errors, { dataVar: 'env' }))
error.errors = ajv.errors
throw error
}
return merge
}
function chooseAjvInstance (defaultInstance, ajvOpts) {
if (ajvOpts instanceof Ajv) {
return ajvOpts
}
let ajv = defaultInstance
if (typeof ajvOpts === 'object' && typeof ajvOpts.customOptions === 'function') {
ajv = ajvOpts.customOptions(getDefaultInstance())
if (!(ajv instanceof Ajv)) {
throw new TypeError('customOptions function must return an instance of Ajv')
}
} else if (typeof ajvOpts === 'object' && typeof ajvOpts.customOptions === 'object') {
ajv = getDefaultInstance(ajvOpts.customOptions)
}
return ajv
}
function getDefaultInstance (overrideOpts = {}) {
return new Ajv({
allErrors: true,
removeAdditional: true,
useDefaults: true,
coerceTypes: true,
allowUnionTypes: true,
addUsedSchema: false,
keywords: [separator],
...overrideOpts
})
}
envSchema.keywords = { separator }
module.exports = envSchema
module.exports.default = envSchema
module.exports.envSchema = envSchema