@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +14 -2
  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/commands/armor.js +1 -1
  12. package/src/cli/commands/custody.js +43 -0
  13. package/src/cli/commands/native.js +1 -1
  14. package/src/cli/dotenvx.js +11 -6
  15. package/src/lib/grammars/envfile.peggy +98 -0
  16. package/src/lib/helpers/bitwardenCustody.js +9 -1
  17. package/src/lib/helpers/encryptedSources.js +15 -0
  18. package/src/lib/helpers/envfileParser.js +2731 -0
  19. package/src/lib/helpers/errors.js +16 -16
  20. package/src/lib/helpers/executeCommand.js +6 -2
  21. package/src/lib/helpers/formatEnvfileSyntaxError.js +24 -0
  22. package/src/lib/helpers/isValidEmail.js +11 -0
  23. package/src/lib/helpers/isValidUrl.js +9 -0
  24. package/src/lib/helpers/onePasswordCustody.js +10 -2
  25. package/src/lib/helpers/parseWithDecryptor.js +9 -1
  26. package/src/lib/helpers/readEnvfile.js +128 -0
  27. package/src/lib/helpers/selectKeyStorage.js +2 -2
  28. package/src/lib/helpers/validate.js +83 -14
  29. package/src/lib/helpers/validateEnvfile.js +24 -0
  30. package/src/lib/main.js +7 -7
  31. package/src/lib/providers/index.js +1 -1
  32. package/src/lib/proxy/configureProxy.js +55 -0
  33. package/src/lib/proxy/eligibleHeaders.js +16 -0
  34. package/src/lib/proxy/prepareProxy.js +24 -0
  35. package/src/lib/proxy/proxyCertificates.js +44 -0
  36. package/src/lib/proxy/proxyForward.js +65 -0
  37. package/src/lib/proxy/proxyPreload.js +21 -0
  38. package/src/lib/proxy/proxyPreloadSource.js +3 -0
  39. package/src/lib/proxy/proxyServer.js +175 -0
  40. package/src/lib/resolvers/envs.js +15 -5
  41. package/src/lib/resolvers/get.js +1 -1
  42. package/src/lib/services/custodyTransfer.js +50 -0
  43. package/src/lib/services/validate.js +58 -0
  44. package/src/lib/transforms/set.js +2 -2
  45. package/src/lib/helpers/validateEnvExample.js +0 -24
@@ -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 .env.example')
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', 'Better Security:')
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 into your OS secret store')
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', 'For Security Teams:')
354
- program.addHelpText('after', ' armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]')
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
- module.exports = { available, configured, get, getSync, set }
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
+ }