@dotenvx/dotenvx 2.26.1 → 2.27.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +48 -40
  3. package/package.json +12 -2
  4. package/src/cli/actions/decrypt.js +3 -3
  5. package/src/cli/actions/encrypt.js +3 -3
  6. package/src/cli/actions/get.js +2 -2
  7. package/src/cli/actions/keypair.js +1 -1
  8. package/src/cli/actions/run.js +46 -42
  9. package/src/cli/actions/set.js +2 -2
  10. package/src/cli/actions/validate.js +21 -33
  11. package/src/cli/dotenvx.js +2 -2
  12. package/src/lib/grammars/envfile.peggy +98 -0
  13. package/src/lib/helpers/encryptedSources.js +15 -0
  14. package/src/lib/helpers/envfileParser.js +2731 -0
  15. package/src/lib/helpers/errors.js +16 -16
  16. package/src/lib/helpers/executeCommand.js +6 -2
  17. package/src/lib/helpers/formatEnvfileSyntaxError.js +24 -0
  18. package/src/lib/helpers/isValidEmail.js +11 -0
  19. package/src/lib/helpers/isValidUrl.js +9 -0
  20. package/src/lib/helpers/parseWithDecryptor.js +9 -1
  21. package/src/lib/helpers/readEnvfile.js +128 -0
  22. package/src/lib/helpers/selectKeyStorage.js +1 -1
  23. package/src/lib/helpers/validate.js +83 -14
  24. package/src/lib/helpers/validateEnvfile.js +24 -0
  25. package/src/lib/main.js +7 -7
  26. package/src/lib/providers/index.js +1 -1
  27. package/src/lib/proxy/configureProxy.js +55 -0
  28. package/src/lib/proxy/eligibleHeaders.js +16 -0
  29. package/src/lib/proxy/prepareProxy.js +24 -0
  30. package/src/lib/proxy/proxyCertificates.js +44 -0
  31. package/src/lib/proxy/proxyForward.js +65 -0
  32. package/src/lib/proxy/proxyPreload.js +21 -0
  33. package/src/lib/proxy/proxyPreloadSource.js +3 -0
  34. package/src/lib/proxy/proxyServer.js +175 -0
  35. package/src/lib/resolvers/envs.js +15 -5
  36. package/src/lib/resolvers/get.js +1 -1
  37. package/src/lib/services/validate.js +58 -0
  38. package/src/lib/transforms/set.js +2 -2
  39. package/src/lib/helpers/validateEnvExample.js +0 -24
@@ -0,0 +1,44 @@
1
+ const { generateKeyPair, randomBytes } = require('node:crypto')
2
+ const { promisify } = require('node:util')
3
+ const forge = require('node-forge')
4
+
5
+ // Only public certificates are persisted. Both signing keys stay in this process.
6
+ module.exports = async function proxyCertificates (hosts = ['localhost']) {
7
+ const generate = () => promisify(generateKeyPair)('rsa', {
8
+ modulusLength: 2048,
9
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
10
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
11
+ })
12
+ const [caKeys, leafKeys] = await Promise.all([generate(), generate()])
13
+ const issuer = [{ name: 'commonName', value: 'dotenvx temporary proxy' }]
14
+ const certificate = (keys, subject) => {
15
+ const cert = forge.pki.createCertificate()
16
+ cert.publicKey = forge.pki.publicKeyFromPem(keys.publicKey)
17
+ cert.serialNumber = '01' + randomBytes(16).toString('hex')
18
+ cert.validity.notBefore = new Date(Date.now() - 60000)
19
+ cert.validity.notAfter = new Date(Date.now() + 24 * 60 * 60 * 1000)
20
+ cert.setSubject(subject)
21
+ cert.setIssuer(issuer)
22
+ return cert
23
+ }
24
+ const ca = certificate(caKeys, issuer)
25
+ ca.setExtensions([
26
+ { name: 'basicConstraints', cA: true, critical: true },
27
+ { name: 'subjectKeyIdentifier' },
28
+ { name: 'authorityKeyIdentifier', keyIdentifier: true },
29
+ { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true }
30
+ ])
31
+ const signingKey = forge.pki.privateKeyFromPem(caKeys.privateKey)
32
+ ca.sign(signingKey, forge.md.sha256.create())
33
+ const leaf = certificate(leafKeys, [{ name: 'commonName', value: hosts[0] || 'localhost' }])
34
+ leaf.setExtensions([
35
+ { name: 'basicConstraints', cA: false, critical: true },
36
+ { name: 'subjectKeyIdentifier' },
37
+ { name: 'authorityKeyIdentifier', keyIdentifier: ca.generateSubjectKeyIdentifier().getBytes() },
38
+ { name: 'keyUsage', digitalSignature: true, keyEncipherment: true, critical: true },
39
+ { name: 'extKeyUsage', serverAuth: true },
40
+ { name: 'subjectAltName', altNames: hosts.map(value => ({ type: 2, value })) }
41
+ ])
42
+ leaf.sign(signingKey, forge.md.sha256.create())
43
+ return { ca: forge.pki.certificateToPem(ca), cert: forge.pki.certificateToPem(leaf), key: leafKeys.privateKey }
44
+ }
@@ -0,0 +1,65 @@
1
+ const eligibleHeaders = require('./eligibleHeaders')
2
+
3
+ // Parent-only forwarding. Never load credential configuration into the application.
4
+ function install (config, nativeFetch = globalThis.fetch) {
5
+ if (typeof nativeFetch !== 'function') throw new Error('Credential proxy requires Node.js with built-in fetch.')
6
+ return async function proxyFetch (input, init) {
7
+ const url = new URL(input instanceof Request ? input.url : input)
8
+ if (!config.credentials.some(item => item.host === url.hostname)) return nativeFetch(input, init)
9
+ if (url.protocol !== 'https:' || (url.port && url.port !== '443') || url.username || url.password) {
10
+ throw new Error('Credential proxy requests must use HTTPS on port 443.')
11
+ }
12
+ const request = new Request(input, init)
13
+ const upstreamHeaders = Object.fromEntries([...request.headers].filter(([name]) => eligibleHeaders(name)))
14
+ const values = Object.entries(upstreamHeaders).map(([name, value]) => {
15
+ // Preserve existing Basic support: its placeholder is encoded on the wire.
16
+ if (name === 'authorization' && value.startsWith('Basic ')) {
17
+ const encoded = value.slice(6)
18
+ const decoded = Buffer.from(encoded, 'base64')
19
+ if (decoded.toString('base64') !== encoded) throw new Error('Invalid Basic authorization.')
20
+ return decoded.toString('utf8')
21
+ }
22
+ return value
23
+ })
24
+ const active = config.credentials.filter(credential => {
25
+ const occurrences = values.reduce((total, value) => total + value.split(credential.placeholder).length - 1, 0)
26
+ if (occurrences && credential.host !== url.hostname) throw new Error('Proxy credential is not allowed at this destination.')
27
+ if (occurrences > 1) throw new Error('Proxy credential must occur in only one eligible header, once.')
28
+ return occurrences === 1
29
+ })
30
+ if (!active.length) throw new Error('Credential proxy requires a proxied environment variable in an eligible header.')
31
+ if (active.length > 8 || active.some(item => item.publicKey !== active[0].publicKey)) {
32
+ throw new Error('Proxy request requires at most eight credentials from the same keypair.')
33
+ }
34
+ const body = ['GET', 'HEAD'].includes(request.method) ? null : Buffer.from(await request.arrayBuffer())
35
+ if (body && body.length > 256 * 1024) throw new Error('Proxy request body exceeds 256 KiB.')
36
+ const credentials = active.map(({ publicKey, placeholder, ciphertext }) => ({ publicKey, placeholder, ciphertext }))
37
+ const metadata = JSON.stringify({ headers: upstreamHeaders, credentials })
38
+ if (Buffer.byteLength(metadata) > 64 * 1024) throw new Error('Proxy request metadata exceeds 64 KiB.')
39
+ const payload = JSON.stringify({
40
+ version: 1,
41
+ request: {
42
+ url: url.href,
43
+ method: request.method,
44
+ headers: upstreamHeaders,
45
+ body: body === null ? null : { encoding: 'base64', data: body.toString('base64') }
46
+ },
47
+ credentials
48
+ })
49
+ if (Buffer.byteLength(payload) > 512 * 1024) throw new Error('Proxy request envelope exceeds 512 KiB.')
50
+ const headers = new Headers({
51
+ Authorization: `Bearer ${config.token}`,
52
+ 'Content-Type': 'application/json'
53
+ })
54
+ if (config.devicePublicKey) headers.set('dotenvx-device-public-key', config.devicePublicKey)
55
+ return nativeFetch(`${config.hostname}/api/proxy`, {
56
+ method: 'POST',
57
+ headers,
58
+ body: payload,
59
+ signal: request.signal,
60
+ redirect: 'error'
61
+ })
62
+ }
63
+ }
64
+
65
+ module.exports = install
@@ -0,0 +1,21 @@
1
+ // Other runtimes use standard proxy/CA variables. Node also needs a bridge for
2
+ // older runtimes and core HTTP clients that do not read proxy variables.
3
+ if (process.env.DOTENVX_PROXY_URL) {
4
+ const http = require('node:http')
5
+ if (typeof http.setGlobalProxyFromEnv === 'function') {
6
+ http.setGlobalProxyFromEnv()
7
+ } else {
8
+ const { ProxyAgent, Agent, Dispatcher, setGlobalDispatcher } = require('undici')
9
+ const proxy = new ProxyAgent(process.env.DOTENVX_PROXY_URL)
10
+ const direct = new Agent()
11
+ class ProxyDispatcher extends Dispatcher {
12
+ dispatch (options, handler) {
13
+ const host = new URL(options.origin).hostname
14
+ return (['localhost', '127.0.0.1', '[::1]'].includes(host) ? direct : proxy).dispatch(options, handler)
15
+ }
16
+ }
17
+ setGlobalDispatcher(new ProxyDispatcher())
18
+ }
19
+ const { createGlobalProxyAgent } = require('global-agent')
20
+ createGlobalProxyAgent({ environmentVariableNamespace: '', forceGlobalAgent: true })
21
+ }
@@ -0,0 +1,3 @@
1
+ // Source installs can resolve dependencies beside the original preload. The
2
+ // executable build replaces this module with the bundled, standalone script.
3
+ module.exports = `require(${JSON.stringify(require.resolve('./proxyPreload'))})\n`
@@ -0,0 +1,175 @@
1
+ const http = require('node:http')
2
+ const https = require('node:https')
3
+ const net = require('node:net')
4
+ const tls = require('node:tls')
5
+ const fs = require('node:fs')
6
+ const os = require('node:os')
7
+ const path = require('node:path')
8
+ const certificates = require('./proxyCertificates')
9
+ const forward = require('./proxyForward')
10
+ const eligibleHeaders = require('./eligibleHeaders')
11
+
12
+ const REQUEST_LIMIT = 256 * 1024
13
+ const RESPONSE_LIMIT = 2 * 1024 * 1024
14
+ const RESPONSE_HEADERS = ['content-type', 'request-id', 'retry-after', 'stripe-version']
15
+
16
+ module.exports = async function startProxy (config, nativeFetch) {
17
+ const fetch = forward(config, nativeFetch)
18
+ const hosts = new Set(config.credentials.map(item => item.host))
19
+ const identity = await certificates(hosts.size ? [...hosts] : ['localhost'])
20
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'dxp-'))
21
+ fs.chmodSync(directory, 0o700)
22
+ const caPath = path.join(directory, 'ca.pem')
23
+ const sockets = new Set()
24
+ const track = socket => {
25
+ sockets.add(socket)
26
+ socket.on('close', () => sockets.delete(socket))
27
+ socket.on('error', () => {})
28
+ socket.setTimeout(30000, () => socket.destroy())
29
+ return socket
30
+ }
31
+ const reject = socket => socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n')
32
+ const servers = new Map()
33
+ for (const host of hosts) {
34
+ const secure = https.createServer({ key: identity.key, cert: identity.cert, ALPNProtocols: ['http/1.1'] }, async (req, res) => {
35
+ const controller = new AbortController()
36
+ res.on('close', () => { if (!res.writableEnded) controller.abort() })
37
+ try {
38
+ const url = new URL(req.url, `https://${host}`)
39
+ if (![host, `${host}:443`].includes(req.headers.host?.toLowerCase()) ||
40
+ url.origin !== `https://${host}` || url.username || url.password || url.hash ||
41
+ !req.url.startsWith('/') || req.url.startsWith('//') ||
42
+ !['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
43
+ res.writeHead(403).end('Proxy request is not allowed.')
44
+ return
45
+ }
46
+ const chunks = []
47
+ let size = 0
48
+ for await (const chunk of req) {
49
+ size += chunk.length
50
+ if (size > REQUEST_LIMIT) {
51
+ res.writeHead(413).end('Proxy request exceeds 256 KiB.')
52
+ return
53
+ }
54
+ chunks.push(chunk)
55
+ }
56
+ const headers = new Headers()
57
+ for (const [name, value] of Object.entries(req.headers)) {
58
+ if (eligibleHeaders(name)) headers.set(name, Array.isArray(value) ? value.join(', ') : value)
59
+ }
60
+ const response = await fetch(url.href, {
61
+ method: req.method,
62
+ headers,
63
+ body: ['GET', 'HEAD'].includes(req.method) ? undefined : Buffer.concat(chunks),
64
+ signal: controller.signal
65
+ })
66
+ const output = []
67
+ let bytes = 0
68
+ if (response.body) {
69
+ for await (const chunk of response.body) {
70
+ bytes += chunk.length
71
+ if (bytes > RESPONSE_LIMIT) throw new Error('response too large')
72
+ output.push(Buffer.from(chunk))
73
+ }
74
+ }
75
+ for (const name of RESPONSE_HEADERS) {
76
+ if (response.headers.has(name)) res.setHeader(name, response.headers.get(name))
77
+ }
78
+ res.writeHead(response.status).end(Buffer.concat(output))
79
+ } catch {
80
+ if (!res.headersSent) res.writeHead(502)
81
+ res.end('Proxy request failed.')
82
+ }
83
+ })
84
+ secure.on('secureConnection', track)
85
+ secure.on('tlsClientError', (_error, socket) => socket.destroy())
86
+ secure.on('upgrade', (_req, socket) => reject(socket))
87
+ secure.requestTimeout = 30000
88
+ secure.headersTimeout = 10000
89
+ servers.set(host, secure)
90
+ }
91
+
92
+ const server = http.createServer((req, res) => {
93
+ // Ordinary HTTP is forwarded without injecting any proxy credential.
94
+ let url
95
+ try { url = new URL(req.url) } catch { res.writeHead(400).end(); return }
96
+ if (url.protocol !== 'http:' || url.username || url.password || hosts.has(url.hostname) || self(url)) {
97
+ res.writeHead(403).end('Proxy request is not allowed.')
98
+ return
99
+ }
100
+ const headers = { ...req.headers, host: url.host }
101
+ for (const name of ['proxy-authorization', 'proxy-connection', 'connection']) delete headers[name]
102
+ const upstream = http.request(url, { method: req.method, headers, agent: false }, response => {
103
+ const responseHeaders = { ...response.headers }
104
+ delete responseHeaders['proxy-authenticate']
105
+ res.writeHead(response.statusCode, responseHeaders)
106
+ response.pipe(res)
107
+ response.on('error', () => res.destroy())
108
+ })
109
+ upstream.on('socket', track)
110
+ upstream.on('error', () => { if (!res.headersSent) res.writeHead(502); res.end('Proxy request failed.') })
111
+ res.on('close', () => upstream.destroy())
112
+ req.pipe(upstream)
113
+ })
114
+ function self (url) {
115
+ return ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname) && Number(url.port) === server.address()?.port
116
+ }
117
+ server.on('connection', track)
118
+ server.on('upgrade', (_req, socket) => reject(socket))
119
+ server.on('connect', (req, socket, head) => {
120
+ let target
121
+ try {
122
+ if (!/^(\[[\da-f:]+\]|[a-z\d.-]+):\d+$/i.test(req.url)) throw new Error('invalid authority')
123
+ target = new URL(`https://${req.url}`)
124
+ if (self(target)) throw new Error('proxy loop')
125
+ } catch { reject(socket); return }
126
+ if (hosts.has(target.hostname)) {
127
+ if (target.port && target.port !== '443') { reject(socket); return }
128
+ socket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
129
+ if (head.length) socket.unshift(head)
130
+ servers.get(target.hostname).emit('connection', socket)
131
+ return
132
+ }
133
+ // Unmanaged HTTPS remains end-to-end encrypted through a standard CONNECT tunnel.
134
+ const upstream = track(net.connect(Number(target.port) || 443, target.hostname.replace(/^\[|\]$/g, '')))
135
+ upstream.on('connect', () => {
136
+ socket.write('HTTP/1.1 200 Connection Established\r\n\r\n')
137
+ if (head.length) upstream.write(head)
138
+ socket.pipe(upstream).pipe(socket)
139
+ })
140
+ upstream.on('error', () => socket.destroy())
141
+ socket.on('close', () => upstream.destroy())
142
+ upstream.on('close', () => socket.destroy())
143
+ })
144
+ server.requestTimeout = 30000
145
+ server.headersTimeout = 10000
146
+ let closed
147
+ const close = () => {
148
+ if (!closed) {
149
+ closed = (async () => {
150
+ for (const socket of sockets) socket.destroy()
151
+ await new Promise(resolve => server.close(resolve))
152
+ fs.rmSync(directory, { recursive: true, force: true })
153
+ })()
154
+ }
155
+ return closed
156
+ }
157
+ try {
158
+ const roots = typeof tls.getCACertificates === 'function' ? tls.getCACertificates('default') : tls.rootCertificates
159
+ const trust = new Set(roots)
160
+ const env = config.env || {}
161
+ for (const name of ['NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE', 'REQUESTS_CA_BUNDLE', 'CURL_CA_BUNDLE', 'GIT_SSL_CAINFO', 'CARGO_HTTP_CAINFO', 'DENO_CERT']) {
162
+ if (env[name]) trust.add(fs.readFileSync(env[name], 'utf8'))
163
+ }
164
+ trust.add(identity.ca)
165
+ fs.writeFileSync(caPath, [...trust].join('\n'), { mode: 0o600 })
166
+ await new Promise((resolve, reject) => {
167
+ server.once('error', reject)
168
+ server.listen(0, '127.0.0.1', () => { server.removeListener('error', reject); resolve() })
169
+ })
170
+ } catch (error) {
171
+ await close()
172
+ throw error
173
+ }
174
+ return { proxyUrl: `http://127.0.0.1:${server.address().port}`, caPath, close }
175
+ }
@@ -54,10 +54,12 @@ function inject (processEnv, parsed) {
54
54
  }
55
55
  }
56
56
 
57
- function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, decryptor }) {
57
+ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, decryptor, proxyCredentials, proxyRules }) {
58
58
  const options = {
59
59
  processEnv,
60
60
  overload,
61
+ proxyCredentials,
62
+ proxyRules,
61
63
  fk: envKeysFilepath
62
64
  }
63
65
 
@@ -74,7 +76,7 @@ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, d
74
76
  return options
75
77
  }
76
78
 
77
- async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden, onStatus }) {
79
+ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, no1Password, noBitwarden, onStatus, proxyCredentials, proxyRules }) {
78
80
  const row = {}
79
81
  row.type = TYPE_ENV
80
82
  row.string = env.value
@@ -90,7 +92,9 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
90
92
  overload,
91
93
  envKeysFilepath,
92
94
  provider,
93
- decryptor
95
+ decryptor,
96
+ proxyCredentials,
97
+ proxyRules
94
98
  })
95
99
 
96
100
  const {
@@ -179,7 +183,7 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider,
179
183
  return row
180
184
  }
181
185
 
182
- async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden, onStatus }) {
186
+ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths, no1Password, noBitwarden, onStatus, proxyCredentials, proxyRules }) {
183
187
  const row = {}
184
188
  row.type = TYPE_ENV_FILE
185
189
  row.filepath = env.value
@@ -197,7 +201,9 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
197
201
  overload,
198
202
  envKeysFilepath: fk,
199
203
  provider,
200
- decryptor
204
+ decryptor,
205
+ proxyCredentials,
206
+ proxyRules
201
207
  })
202
208
 
203
209
  const {
@@ -319,6 +325,8 @@ async function envs (options = {}) {
319
325
  readableFilepaths,
320
326
  no1Password,
321
327
  noBitwarden,
328
+ proxyCredentials: options.proxyCredentials,
329
+ proxyRules: options.proxyRules,
322
330
  onStatus: options.onStatus
323
331
  }))
324
332
  } else if (env.type === TYPE_ENV) {
@@ -331,6 +339,8 @@ async function envs (options = {}) {
331
339
  decryptor,
332
340
  no1Password,
333
341
  noBitwarden,
342
+ proxyCredentials: options.proxyCredentials,
343
+ proxyRules: options.proxyRules,
334
344
  onStatus: options.onStatus
335
345
  }))
336
346
  }
@@ -50,7 +50,7 @@ function buildOptions (options, processEnv) {
50
50
  processEnv,
51
51
  envKeysFilepath: options.envKeysFilepath || options.envKeysFile || null,
52
52
  noArmor: options.noArmor,
53
- noKeychain: options.noKeychain,
53
+ noNative: options.noNative,
54
54
  no1Password: options.no1Password,
55
55
  noBitwarden: options.noBitwarden,
56
56
  onStatus: options.onStatus
@@ -0,0 +1,58 @@
1
+ const envsResolver = require('../resolvers/envs')
2
+ const Session = require('../../db/session')
3
+ const buildCommandEnvs = require('../helpers/buildCommandEnvs')
4
+ const resolveEnvKeysFile = require('../helpers/resolveEnvKeysFile')
5
+ const readEnvfile = require('../helpers/readEnvfile')
6
+ const validateEnvfile = require('../helpers/validateEnvfile')
7
+ const normalizeDotenvConfigPath = require('../helpers/normalizeDotenvConfigPath')
8
+ const Errors = require('../helpers/errors')
9
+ const { determine } = require('../helpers/envResolution')
10
+
11
+ // Load once and validate the final values; callers own presentation and execution.
12
+ module.exports = async function validate ({ envs = [], options = {}, processEnv = { ...process.env }, requireEnvfile = true, command, onStatus } = {}) {
13
+ envs = buildCommandEnvs(normalizeDotenvConfigPath(envs, processEnv), options.convention)
14
+ envs = determine(envs, processEnv)
15
+ const schema = readEnvfile(undefined, envs.filter(env => env.type === 'envFile').map(env => env.value))
16
+ if (requireEnvfile && !schema.exists) throw new Errors().envfileRequired()
17
+
18
+ const session = new Session()
19
+ const proxyToken = options.token || processEnv.DOTENVX_TOKEN
20
+ const noArmor = options.armor === false || (!proxyToken && (await session.noArmor()))
21
+ if (schema.proxyRules.size > 0 && noArmor) throw new Error('Envfile proxy requires Armor. Enable Armor and authenticate before running.')
22
+ const proxyCredentials = noArmor ? undefined : []
23
+ const { processedEnvs, readableFilepaths } = await envsResolver({
24
+ envs,
25
+ proxyRules: schema.proxyRules,
26
+ proxyCredentials,
27
+ overload: options.overload,
28
+ processEnv,
29
+ envKeysFile: resolveEnvKeysFile(options.envKeysFile),
30
+ noArmor,
31
+ noNative: options.native === false || options.noNative === true,
32
+ no1Password: options['1password'] === false || options.no1Password === true,
33
+ noBitwarden: options.bitwarden === false || options.noBitwarden === true,
34
+ token: options.token,
35
+ command,
36
+ onStatus
37
+ })
38
+
39
+ let proxyError
40
+ for (const name of schema.proxyRules.keys()) {
41
+ if (processEnv[name] !== undefined && !(proxyCredentials || []).some(credential => credential.name === name && credential.placeholder === processEnv[name])) {
42
+ proxyError = new Error(`Envfile proxy requires an encrypted ${name} loaded from an env file. Remove plaintext or shell overrides, or use --overload.`)
43
+ break
44
+ }
45
+ }
46
+
47
+ return {
48
+ processEnv,
49
+ processedEnvs,
50
+ readableFilepaths,
51
+ hasEnvfile: schema.exists,
52
+ proxyCredentials,
53
+ proxyToken,
54
+ session,
55
+ proxyError,
56
+ validationError: validateEnvfile(schema, processEnv, processedEnvs)
57
+ }
58
+ }
@@ -28,7 +28,7 @@ async function setTransform (options = {}) {
28
28
  const fk = options.fk || '.env.keys'
29
29
  const noArmor = options.noArmor
30
30
  let storage
31
- const noKeychain = options.noKeychain
31
+ const noNative = options.noNative
32
32
  const noCreate = options.noCreate
33
33
  const noEncrypt = !options.encrypt || isPlainKey(key)
34
34
 
@@ -147,7 +147,7 @@ async function setTransform (options = {}) {
147
147
  all: true,
148
148
  envKeysFile: fk,
149
149
  noArmor,
150
- noKeychain,
150
+ noNative,
151
151
  no1Password: options.no1Password,
152
152
  noBitwarden: options.noBitwarden
153
153
  })
@@ -1,24 +0,0 @@
1
- const fs = require('fs')
2
- const { scan } = require('@dotenvx/primitives')
3
-
4
- const Errors = require('./errors')
5
- const validate = require('./validate')
6
-
7
- function validateEnvExample (env = process.env, options = {}) {
8
- const filepath = options.filepath || '.env.example'
9
-
10
- if (!fs.existsSync(filepath)) {
11
- return new Errors().missingEnvExample()
12
- }
13
-
14
- const exampleSrc = fs.readFileSync(filepath, 'utf8')
15
- const { parsed: example, comments } = scan(exampleSrc)
16
- const validation = validate(example, env, { comments })
17
-
18
- if (!validation.valid) {
19
- const message = validation.errors.map(error => error.message).join('; ')
20
- return new Errors({ message }).validationFailed()
21
- }
22
- }
23
-
24
- module.exports = validateEnvExample