@dotenvx/dotenvx 1.16.1 → 1.18.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 CHANGED
@@ -2,7 +2,46 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.16.1...main)
5
+ ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.18.0...main)
6
+
7
+ ## 1.18.0
8
+
9
+ ### Added
10
+
11
+ * `set` and `encrypt` preserve leading spaces ([#395](https://github.com/dotenvx/dotenvx/pull/395/))
12
+
13
+ ```sh
14
+ HELLO=world
15
+ ```
16
+
17
+ ### Changed
18
+
19
+ * improve escape and quote handling for `set`, `encrypt`, and `decrypt` ([#395](https://github.com/dotenvx/dotenvx/pull/395))
20
+ * 🐞 fix `encrypt`, then `decrypt`, then `encrypt` on a json value ([#377](https://github.com/dotenvx/dotenvx/issues/377))
21
+
22
+ Note: the underlying `replace` engine to support these changes now wraps your values in single quotes. the prior `replace` engine wrapped in double quotes.
23
+
24
+ So where your `.env` used to look like this with double quotes:
25
+
26
+ ```sh
27
+ HELLO="encrypted:1234"
28
+ API_KEY="encrypted:5678"
29
+ ```
30
+
31
+ It will now begin looking like this with single quotes:
32
+
33
+ ```sh
34
+ HELLO='encrypted:1234'
35
+ API_KEY='encrypted:5678'
36
+ ```
37
+
38
+ It's an aesthetic side effect only. Your values will continue to be decrypted and encrypted correctly.
39
+
40
+ ## 1.17.0
41
+
42
+ ### Added
43
+
44
+ * add `--format=eval` option for `get` ([#393](https://github.com/dotenvx/dotenvx/pull/393))
6
45
 
7
46
  ## 1.16.1
8
47
 
package/README.md CHANGED
@@ -260,6 +260,18 @@ More examples
260
260
  Hello World
261
261
  ```
262
262
 
263
+ </details>
264
+ * <details><summary>Kotlin 📐</summary><br>
265
+
266
+ ```sh
267
+ $ echo "HELLO=World" > .env
268
+ $ echo 'fun main() { val hello = System.getenv("HELLO") ?: ""; println("Hello $hello") }' > index.kt
269
+ $ kotlinc index.kt -include-runtime -d index.jar
270
+
271
+ $ dotenvx run -- java -jar index.jar
272
+ Hello World
273
+ ```
274
+
263
275
  </details>
264
276
  * <details><summary>.NET 🔵</summary><br>
265
277
 
@@ -1072,6 +1084,33 @@ More examples
1072
1084
  ```
1073
1085
 
1074
1086
  </details>
1087
+ * <details><summary>`get --format eval`</summary><br>
1088
+
1089
+ Return an `eval`-ready shell formatted response of all key/value pairs in a `.env` file.
1090
+
1091
+ ```sh
1092
+ $ echo "HELLO=World" > .env
1093
+ $ echo "KEY=value" >> .env
1094
+
1095
+ $ dotenvx get --format eval
1096
+ HELLO="World"
1097
+ KEY="value"
1098
+ ```
1099
+
1100
+ Note that this exports newlines and quoted strings.
1101
+
1102
+ This can be useful for more complex .env values (spaces, escaped characters, quotes, etc) combined with `eval` on the command line.
1103
+
1104
+ ```sh
1105
+ $ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
1106
+ $ eval $(dotenvx get --format=eval) node index.js
1107
+ Hello value World
1108
+ ```
1109
+
1110
+ Be careful with `eval` as it allows for arbitrary execution of commands. Prefer `dotenvx run --` but in some cases `eval` is a sharp knife that is useful to have.
1111
+
1112
+ </details>
1113
+
1075
1114
  * <details><summary>`get --all`</summary><br>
1076
1115
 
1077
1116
  Return preset machine envs as well.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.16.1",
2
+ "version": "1.18.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -1,6 +1,7 @@
1
1
  const { logger } = require('./../../shared/logger')
2
2
 
3
3
  const conventions = require('./../../lib/helpers/conventions')
4
+ const escape = require('./../../lib/helpers/escape')
4
5
 
5
6
  const main = require('./../../lib/main')
6
7
 
@@ -23,8 +24,15 @@ function get (key) {
23
24
  const results = main.get(key, envs, options.overload, process.env.DOTENV_KEY, options.all)
24
25
 
25
26
  if (typeof results === 'object' && results !== null) {
26
- // inline shell format - env $(dotenvx get --format shell) your-command
27
- if (options.format === 'shell') {
27
+ if (options.format === 'eval') {
28
+ let inline = ''
29
+ for (const [key, value] of Object.entries(results)) {
30
+ inline += `${key}=${escape(value)}\n`
31
+ }
32
+ inline = inline.trim()
33
+
34
+ console.log(inline)
35
+ } else if (options.format === 'shell') {
28
36
  let inline = ''
29
37
  for (const [key, value] of Object.entries(results)) {
30
38
  inline += `${key}=${value} `
@@ -32,7 +40,6 @@ function get (key) {
32
40
  inline = inline.trim()
33
41
 
34
42
  console.log(inline)
35
- // json format
36
43
  } else {
37
44
  let space = 0
38
45
  if (options.prettyPrint) {
@@ -75,7 +75,7 @@ program.command('get')
75
75
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\'])')
76
76
  .option('-a, --all', 'include all machine envs as well')
77
77
  .option('-pp, --pretty-print', 'pretty print output')
78
- .option('--format <type>', 'format of the output (json, shell)', 'json')
78
+ .option('--format <type>', 'format of the output (json, shell, eval)', 'json')
79
79
  .action(function (...args) {
80
80
  this.envs = envs
81
81
 
@@ -0,0 +1,5 @@
1
+ function escape (value) {
2
+ return JSON.stringify(value)
3
+ }
4
+
5
+ module.exports = escape
@@ -0,0 +1,5 @@
1
+ function escapeForRegex (str) {
2
+ return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d')
3
+ }
4
+
5
+ module.exports = escapeForRegex
@@ -1,38 +1,55 @@
1
+ const util = require('util')
1
2
  const dotenv = require('dotenv')
2
3
 
3
- function replace (src, key, value) {
4
+ const escapeForRegex = require('./escapeForRegex')
5
+
6
+ function replace (src, key, replaceValue) {
4
7
  let output
5
- let formatted = `${key}="${value}"`
8
+ let escapedValue = util.inspect(replaceValue, { showHidden: false, depth: null, colors: false })
9
+ if (replaceValue.includes('\n')) {
10
+ escapedValue = JSON.stringify(replaceValue) // use JSON stringify if string contains newlines
11
+ escapedValue = escapedValue.replace(/\\n/g, '\n') // fix up newlines
12
+ escapedValue = escapedValue.replace(/\\r/g, '\r')
13
+ }
14
+ let newPart = `${key}=${escapedValue}`
6
15
 
7
16
  const parsed = dotenv.parse(src)
8
17
  if (Object.prototype.hasOwnProperty.call(parsed, key)) {
9
- const regex = new RegExp(
10
- // Match the key at the start of a line, following a newline, or prefaced by export
11
- `(^|\\n)\\s*(export\\s+)?${key}\\s*=\\s*` +
12
- // `(^|\\n)${key}\\s*=\\s*` +
13
- // Non-capturing group to handle different types of quotations and unquoted values
14
- '(?:' +
15
- '(["\'`])' + // Match an opening quote
16
- '.*?' + // Non-greedy match for any characters within quotes
17
- // '\\2' + // Match the corresponding closing quote
18
- '\\3' + // Match the corresponding closing quote
19
- '|' +
20
- // Match unquoted values; account for escaped newlines
21
- '(?:[^#\\n\\\\]|\\\\.)*' + // Use non-capturing group for any character except #, newline, or backslash, or any escaped character
22
- ')',
23
- 'gs' // Global and dotAll mode to treat string as single line
18
+ const originalValue = parsed[key]
19
+ const escapedOriginalValue = escapeForRegex(originalValue)
20
+
21
+ // conditionally enforce end of line
22
+ let enforceEndOfLine = ''
23
+ if (escapedOriginalValue === '') {
24
+ enforceEndOfLine = '$' // EMPTY scenario
25
+ }
26
+
27
+ const currentPart = new RegExp(
28
+ '^' + // start of line
29
+ '(\\s*)?' + // spaces
30
+ '(export\\s+)?' + // export
31
+ key + // KEY
32
+ '\\s*=\\s*' + // spaces (KEY = value)
33
+ '["\'`]?' + // open quote
34
+ escapedOriginalValue + // escaped value
35
+ '["\'`]?' + // close quote
36
+ enforceEndOfLine
37
+ ,
38
+ 'gm' // (g)lobal (m)ultiline
24
39
  )
25
40
 
26
- output = src.replace(regex, `$1$2${formatted}`)
41
+ // $1 preserves spaces
42
+ // $2 preserves export
43
+ output = src.replace(currentPart, `$1$2${newPart}`)
27
44
  } else {
28
45
  // append
29
46
  if (src.endsWith('\n')) {
30
- formatted = formatted + '\n'
47
+ newPart = newPart + '\n'
31
48
  } else {
32
- formatted = '\n' + formatted
49
+ newPart = '\n' + newPart
33
50
  }
34
51
 
35
- output = src + formatted
52
+ output = src + newPart
36
53
  }
37
54
 
38
55
  return output
@@ -83,7 +83,7 @@ class Genexample {
83
83
  if (key in parsed) {
84
84
  preExisted[key] = parsed[key]
85
85
  } else {
86
- exampleSrc += `${key}=""\n`
86
+ exampleSrc += `${key}=''\n`
87
87
 
88
88
  addedKeys.add(key)
89
89