@dotenvx/dotenvx 2.26.0 → 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.
- package/CHANGELOG.md +14 -2
- package/README.md +48 -40
- package/package.json +12 -2
- package/src/cli/actions/decrypt.js +3 -3
- package/src/cli/actions/encrypt.js +3 -3
- package/src/cli/actions/get.js +2 -2
- package/src/cli/actions/keypair.js +1 -1
- package/src/cli/actions/run.js +46 -42
- package/src/cli/actions/set.js +2 -2
- package/src/cli/actions/validate.js +21 -33
- package/src/cli/commands/armor.js +1 -1
- package/src/cli/commands/custody.js +43 -0
- package/src/cli/commands/native.js +1 -1
- package/src/cli/dotenvx.js +11 -6
- package/src/lib/grammars/envfile.peggy +98 -0
- package/src/lib/helpers/bitwardenCustody.js +9 -1
- package/src/lib/helpers/encryptedSources.js +15 -0
- package/src/lib/helpers/envfileParser.js +2731 -0
- package/src/lib/helpers/errors.js +16 -16
- package/src/lib/helpers/executeCommand.js +6 -2
- package/src/lib/helpers/formatEnvfileSyntaxError.js +24 -0
- package/src/lib/helpers/isValidEmail.js +11 -0
- package/src/lib/helpers/isValidUrl.js +9 -0
- package/src/lib/helpers/onePasswordCustody.js +10 -2
- package/src/lib/helpers/parseWithDecryptor.js +9 -1
- package/src/lib/helpers/readEnvfile.js +128 -0
- package/src/lib/helpers/selectKeyStorage.js +2 -2
- package/src/lib/helpers/validate.js +83 -14
- package/src/lib/helpers/validateEnvfile.js +24 -0
- package/src/lib/main.js +7 -7
- package/src/lib/providers/index.js +1 -1
- package/src/lib/proxy/configureProxy.js +55 -0
- package/src/lib/proxy/eligibleHeaders.js +16 -0
- package/src/lib/proxy/prepareProxy.js +24 -0
- package/src/lib/proxy/proxyCertificates.js +44 -0
- package/src/lib/proxy/proxyForward.js +65 -0
- package/src/lib/proxy/proxyPreload.js +21 -0
- package/src/lib/proxy/proxyPreloadSource.js +3 -0
- package/src/lib/proxy/proxyServer.js +175 -0
- package/src/lib/resolvers/envs.js +15 -5
- package/src/lib/resolvers/get.js +1 -1
- package/src/lib/services/custodyTransfer.js +50 -0
- package/src/lib/services/validate.js +58 -0
- package/src/lib/transforms/set.js +2 -2
- package/src/lib/helpers/validateEnvExample.js +0 -24
package/src/cli/dotenvx.js
CHANGED
|
@@ -73,7 +73,6 @@ program.command('run')
|
|
|
73
73
|
.option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
|
|
74
74
|
.option('--redact', 'redact injected values except keys ending in _PLAIN', false)
|
|
75
75
|
.option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
|
|
76
|
-
.option('--validate', 'validate against .env.example', false)
|
|
77
76
|
.option('--strict', 'process.exit(1) on any errors', false)
|
|
78
77
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
79
78
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
@@ -224,7 +223,8 @@ program.command('ls')
|
|
|
224
223
|
|
|
225
224
|
// dotenvx validate
|
|
226
225
|
program.command('validate')
|
|
227
|
-
.description('validate .env file(s) against
|
|
226
|
+
.description('validate .env file(s) against Envfile')
|
|
227
|
+
.option('--strict', 'process.exit(1) on any errors, including missing env files', false)
|
|
228
228
|
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
|
|
229
229
|
.option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
|
|
230
230
|
.option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
|
|
@@ -346,14 +346,19 @@ program.command('help [command]')
|
|
|
346
346
|
|
|
347
347
|
// security sections (hidden commands advertised here)
|
|
348
348
|
program.addHelpText('after', ' ')
|
|
349
|
-
program.addHelpText('after', '
|
|
349
|
+
program.addHelpText('after', 'Local Custody:')
|
|
350
350
|
program.addHelpText('after', ' lock ⊡ lock private keys with a local passphrase')
|
|
351
|
-
program.addHelpText('after', ' native ⌥ move private keys
|
|
351
|
+
program.addHelpText('after', ' native ⌥ move private keys in/out of your OS secret store')
|
|
352
|
+
program.addHelpText('after', ' 1password □ move private keys in/out of 1Password')
|
|
353
|
+
program.addHelpText('after', ' bitwarden □ move private keys in/out of Bitwarden')
|
|
352
354
|
program.addHelpText('after', ' ')
|
|
353
|
-
program.addHelpText('after', '
|
|
354
|
-
program.addHelpText('after', ' armor ⛨ move private keys
|
|
355
|
+
program.addHelpText('after', 'Managed Custody:')
|
|
356
|
+
program.addHelpText('after', ' armor ⛨ move private keys in/out of Dotenvx Armor [www.dotenvx.com/armor]')
|
|
355
357
|
program.addHelpText('after', ' curl ⛨ call authenticated api Dotenvx Armor [www.dotenvx.com/armor]')
|
|
356
358
|
|
|
359
|
+
require('./commands/custody')(program.command('1password', { hidden: true }), '1Password', '../../lib/helpers/onePasswordCustody')
|
|
360
|
+
require('./commands/custody')(program.command('bitwarden', { hidden: true }), 'Bitwarden', '../../lib/helpers/bitwardenCustody')
|
|
361
|
+
|
|
357
362
|
// dotenvx native
|
|
358
363
|
require('./commands/native')(program.command('native', { hidden: true }))
|
|
359
364
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Document
|
|
2
|
+
= _ encrypted:EncryptedDefault? _ entries:((FileBlock / Declaration) _)* {
|
|
3
|
+
return { encrypted: encrypted === true, declarations: entries.map(item => item[0]).filter(item => !item.file), files: entries.map(item => item[0]).filter(item => item.file) };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
FileBlock
|
|
7
|
+
= "file" [ \t]+ file:Filename [ \t]+ "do" [ \t]* Comment? Newline _ encrypted:EncryptedDefault? _ declarations:(Declaration _)* "end" [ \t]* Comment? (Newline / !.) {
|
|
8
|
+
return { file, encrypted, declarations: declarations.map(item => item[0]) };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
Filename "a quoted filename"
|
|
12
|
+
= '"' value:$([^"\r\n\0]+) '"' { return value; }
|
|
13
|
+
/ "'" value:$([^'\r\n\0]+) "'" { return value; }
|
|
14
|
+
|
|
15
|
+
EncryptedDefault
|
|
16
|
+
= "encrypted" [ \t]+ value:Boolean [ \t]* Comment? (Newline / !.) { return value; }
|
|
17
|
+
|
|
18
|
+
Declaration
|
|
19
|
+
= "env" [ \t]+ name:Name options:Option* [ \t]* Comment? (Newline / !.) {
|
|
20
|
+
const declaration = { name };
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
for (const option of options) {
|
|
23
|
+
if (seen.has(option.key)) error(`Duplicate Envfile option: ${option.key}`);
|
|
24
|
+
if ((option.key === "optional" && seen.has("required")) ||
|
|
25
|
+
(option.key === "required" && seen.has("optional"))) {
|
|
26
|
+
error("Cannot combine required and optional in an Envfile declaration");
|
|
27
|
+
}
|
|
28
|
+
seen.add(option.key);
|
|
29
|
+
if (option.key === "optional") declaration.required = !option.value;
|
|
30
|
+
else declaration[option.key] = option.value;
|
|
31
|
+
}
|
|
32
|
+
return declaration;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Option
|
|
36
|
+
= [ \t]* "," _ "proxy" [ \t]* ":" _ value:(ProxyOptions / Disabled) { return { key: "proxy", value }; }
|
|
37
|
+
/ [ \t]* "," _ "required" [ \t]* ":" _ value:Boolean { return { key: "required", value }; }
|
|
38
|
+
/ [ \t]* "," _ "optional" [ \t]* ":" _ value:Boolean { return { key: "optional", value }; }
|
|
39
|
+
/ [ \t]* "," _ "encrypted" [ \t]* ":" _ value:Boolean { return { key: "encrypted", value }; }
|
|
40
|
+
/ [ \t]* "," _ "type" [ \t]* ":" _ value:Type { return { key: "type", value }; }
|
|
41
|
+
/ [ \t]* "," _ "enum" [ \t]* ":" _ value:Enum { return { key: "enum", value }; }
|
|
42
|
+
/ [ \t]* "," _ key:("min" / "max") [ \t]* ":" _ value:Integer { return { key, value }; }
|
|
43
|
+
|
|
44
|
+
Integer "an integer"
|
|
45
|
+
= value:$([+-]? [0-9]+) { return value; }
|
|
46
|
+
|
|
47
|
+
Enum
|
|
48
|
+
= "[" _ first:EnumValue rest:(_ "," _ value:EnumValue { return value; })* _ ","? _ "]" {
|
|
49
|
+
return [first, ...rest];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
EnumValue
|
|
53
|
+
= '"' chars:(Escape / [^"\\\r\n])* '"' { return chars.join(""); }
|
|
54
|
+
/ "'" chars:(Escape / [^'\\\r\n])* "'" { return chars.join(""); }
|
|
55
|
+
/ Integer
|
|
56
|
+
|
|
57
|
+
Escape
|
|
58
|
+
= "\\" char:["'\\nrt] {
|
|
59
|
+
return ({ n: "\n", r: "\r", t: "\t" })[char] || char;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Type "a supported type (integer, boolean, port, url, email, or ip)"
|
|
63
|
+
= ('"integer"' / "'integer'") { return "integer"; }
|
|
64
|
+
/ ('"boolean"' / "'boolean'") { return "boolean"; }
|
|
65
|
+
/ ('"port"' / "'port'") { return "port"; }
|
|
66
|
+
/ ('"url"' / "'url'") { return "url"; }
|
|
67
|
+
/ ('"email"' / "'email'") { return "email"; }
|
|
68
|
+
/ ('"ip"' / "'ip'") { return "ip"; }
|
|
69
|
+
|
|
70
|
+
Boolean "true or false"
|
|
71
|
+
= "true" { return true; }
|
|
72
|
+
/ Disabled
|
|
73
|
+
|
|
74
|
+
ProxyOptions
|
|
75
|
+
= "{" _ "domain" [ \t]* ":" _ domain:Destination _ ","? _ "}" { return { domain }; }
|
|
76
|
+
|
|
77
|
+
Disabled
|
|
78
|
+
= "false" { return false; }
|
|
79
|
+
|
|
80
|
+
Destination "a quoted domain"
|
|
81
|
+
= '"' host:$([A-Za-z0-9.-]+) '"' { return host; }
|
|
82
|
+
/ "'" host:$([A-Za-z0-9.-]+) "'" { return host; }
|
|
83
|
+
|
|
84
|
+
Name "a quoted variable name"
|
|
85
|
+
= '"' name:Identifier '"' { return name; }
|
|
86
|
+
/ "'" name:Identifier "'" { return name; }
|
|
87
|
+
|
|
88
|
+
Identifier
|
|
89
|
+
= $([A-Za-z_] [A-Za-z0-9_]*)
|
|
90
|
+
|
|
91
|
+
Comment
|
|
92
|
+
= "#" [^\r\n]*
|
|
93
|
+
|
|
94
|
+
Newline
|
|
95
|
+
= "\r\n" / "\n" / "\r"
|
|
96
|
+
|
|
97
|
+
_
|
|
98
|
+
= ([ \t] / Newline / Comment)*
|
|
@@ -159,4 +159,12 @@ async function set (publicKey, privateKey) {
|
|
|
159
159
|
new Session().createStore().set(`${PREFIX}${publicKey}`, Buffer.from(JSON.stringify(loc)).toString('base64'))
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
async function remove (publicKey) {
|
|
163
|
+
const loc = location(publicKey)
|
|
164
|
+
if (!loc) return
|
|
165
|
+
await authenticate(loc)
|
|
166
|
+
await run(['delete', 'item', loc.item])
|
|
167
|
+
new Session().openStore().delete(`${PREFIX}${publicKey}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { available, configured, get, getSync, set, delete: remove }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { scan, encrypted } = require('@dotenvx/primitives')
|
|
2
|
+
|
|
3
|
+
// Follow actual injection order, so shell overrides and --overload retain their semantics.
|
|
4
|
+
module.exports = function encryptedSources (processedEnvs) {
|
|
5
|
+
const keys = new Set()
|
|
6
|
+
for (const row of processedEnvs || []) {
|
|
7
|
+
const { parsed } = scan(row.src || row.string || '')
|
|
8
|
+
for (const [key, value] of Object.entries(row.injected || {})) {
|
|
9
|
+
const original = parsed[key]?.at(-1)
|
|
10
|
+
if (encrypted(original) && !encrypted(value)) keys.add(key)
|
|
11
|
+
else keys.delete(key)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return keys
|
|
15
|
+
}
|