@dotenvx/dotenvx 2.0.0 → 2.1.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 +16 -1
- package/package.json +1 -1
- package/src/cli/actions/get.js +6 -0
- package/src/cli/actions/set.js +36 -0
- package/src/cli/dotenvx.js +2 -2
- package/src/lib/helpers/cryptography/index.js +2 -1
- package/src/lib/helpers/cryptography/{isPublicKey.js → isDotenvPublicKey.js} +2 -2
- package/src/lib/helpers/cryptography/isPlainKey.js +7 -0
- package/src/lib/helpers/errors.js +14 -1
- package/src/lib/helpers/prompts.js +46 -0
- package/src/lib/main.js +1 -1
- package/src/lib/transforms/encrypt.js +2 -3
- package/src/lib/transforms/set.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
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/v2.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.1.0...main)
|
|
6
|
+
|
|
7
|
+
## [2.1.0](https://github.com/dotenvx/dotenvx/compare/v2.0.0...v2.1.0) (2026-07-02)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add interactive input mode for `set` ([#862](https://github.com/dotenvx/dotenvx/pull/862))
|
|
12
|
+
* Support leaving `_PLAIN` suffixed keys unencrypted during `set` and `encrypt` ([#862](https://github.com/dotenvx/dotenvx/pull/862))
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
* Return exit code 1 from `get` when decryption errors are reported ([#862](https://github.com/dotenvx/dotenvx/pull/862))
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
* Add Windows PowerShell coverage for npm shim encryption of `.env.local` files
|
|
6
21
|
|
|
7
22
|
## [2.0.0](https://github.com/dotenvx/dotenvx/compare/v1.76.0...v2.0.0) (2026-06-30)
|
|
8
23
|
|
package/package.json
CHANGED
package/src/cli/actions/get.js
CHANGED
|
@@ -19,6 +19,7 @@ async function get (key) {
|
|
|
19
19
|
|
|
20
20
|
const prettyPrint = options.prettyPrint || options.pp
|
|
21
21
|
const ignore = options.ignore || []
|
|
22
|
+
let errorCount = 0
|
|
22
23
|
|
|
23
24
|
let envs = []
|
|
24
25
|
// handle shorthand conventions - like --convention=nextjs
|
|
@@ -52,6 +53,7 @@ async function get (key) {
|
|
|
52
53
|
continue // ignore error
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
errorCount += 1
|
|
55
57
|
logger.error(error.messageWithHelp)
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -97,6 +99,10 @@ async function get (key) {
|
|
|
97
99
|
console.log(JSON.stringify(parsed, null, space))
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
|
|
103
|
+
if (errorCount > 0) {
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
100
106
|
} catch (error) {
|
|
101
107
|
if (spinner) spinner.stop()
|
|
102
108
|
catchAndLog(error)
|
package/src/cli/actions/set.js
CHANGED
|
@@ -5,6 +5,8 @@ const setTransform = require('./../../lib/transforms/set')
|
|
|
5
5
|
|
|
6
6
|
const catchAndLog = require('../../lib/helpers/catchAndLog')
|
|
7
7
|
const createSpinner = require('../../lib/helpers/createSpinner')
|
|
8
|
+
const Errors = require('../../lib/helpers/errors')
|
|
9
|
+
const prompts = require('../../lib/helpers/prompts')
|
|
8
10
|
const Session = require('../../db/session')
|
|
9
11
|
const normalizeArmorAliases = require('./normalizeArmorAliases')
|
|
10
12
|
|
|
@@ -13,9 +15,43 @@ async function set (key, value) {
|
|
|
13
15
|
|
|
14
16
|
let encrypt = true
|
|
15
17
|
let settingMessage = 'encrypting'
|
|
18
|
+
let settingSymbol = '◈'
|
|
16
19
|
if (options.plain) {
|
|
17
20
|
encrypt = false
|
|
18
21
|
settingMessage = 'setting'
|
|
22
|
+
settingSymbol = '◇'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof value === 'undefined') {
|
|
26
|
+
if (!process.stdin.isTTY) {
|
|
27
|
+
catchAndLog(new Errors({ key }).missingValue())
|
|
28
|
+
process.exit(1)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
value = await prompts.password({
|
|
34
|
+
message: key,
|
|
35
|
+
prefix: settingSymbol,
|
|
36
|
+
separator: '='
|
|
37
|
+
}, {
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stderr
|
|
40
|
+
})
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code === 'PROMPT_CANCELLED') {
|
|
43
|
+
process.exit(130)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (value === '') {
|
|
52
|
+
catchAndLog(new Errors({ key }).missingValue())
|
|
53
|
+
process.exit(1)
|
|
54
|
+
return
|
|
19
55
|
}
|
|
20
56
|
|
|
21
57
|
const spinner = await createSpinner({ ...options, text: settingMessage })
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -103,12 +103,12 @@ program.command('get')
|
|
|
103
103
|
|
|
104
104
|
// dotenvx set
|
|
105
105
|
program.command('set')
|
|
106
|
-
.usage('<KEY>
|
|
106
|
+
.usage('<KEY> [value] [options]')
|
|
107
107
|
.description('encrypt a single environment variable')
|
|
108
108
|
.addHelpText('after', examples.set)
|
|
109
109
|
.allowUnknownOption()
|
|
110
110
|
.argument('KEY', 'KEY')
|
|
111
|
-
.argument('value', 'value')
|
|
111
|
+
.argument('[value]', 'value')
|
|
112
112
|
.option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
|
|
113
113
|
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
|
|
114
114
|
.option('-c, --encrypt', 'encrypt value', true)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
decryptKeyValue: require('./decryptKeyValue'),
|
|
3
|
-
|
|
3
|
+
isDotenvPublicKey: require('./isDotenvPublicKey'),
|
|
4
|
+
isPlainKey: require('./isPlainKey'),
|
|
4
5
|
mutateKeysSrc: require('./mutateKeysSrc'),
|
|
5
6
|
mutateSrc: require('./mutateSrc')
|
|
6
7
|
}
|
|
@@ -18,7 +18,8 @@ const ISSUE_BY_CODE = {
|
|
|
18
18
|
MISSING_KEY: 'https://github.com/dotenvx/dotenvx/issues/759',
|
|
19
19
|
MISSING_LOG_LEVEL: 'must be valid log level',
|
|
20
20
|
MISSING_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/464',
|
|
21
|
-
MISSING_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/
|
|
21
|
+
MISSING_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/865',
|
|
22
|
+
MISSING_VALUE: 'https://github.com/dotenvx/dotenvx/issues/864',
|
|
22
23
|
PRECOMMIT_HOOK_MODIFY_FAILED: 'try again or report error',
|
|
23
24
|
WRONG_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/466'
|
|
24
25
|
}
|
|
@@ -278,6 +279,18 @@ class Errors {
|
|
|
278
279
|
return e
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
missingValue () {
|
|
283
|
+
const code = 'MISSING_VALUE'
|
|
284
|
+
const message = `[${code}] missing value (${this.key})`
|
|
285
|
+
const help = `fix: [${ISSUE_BY_CODE[code]}]`
|
|
286
|
+
|
|
287
|
+
const e = new Error(message)
|
|
288
|
+
e.code = code
|
|
289
|
+
e.help = help
|
|
290
|
+
e.messageWithHelp = `${message}. ${help}`
|
|
291
|
+
return e
|
|
292
|
+
}
|
|
293
|
+
|
|
281
294
|
precommitHookModifyFailed () {
|
|
282
295
|
const code = 'PRECOMMIT_HOOK_MODIFY_FAILED'
|
|
283
296
|
const message = `[${code}] failed to modify pre-commit hook: ${this.error.message}`
|
|
@@ -26,6 +26,18 @@ function enquirerOptions (context = {}) {
|
|
|
26
26
|
return options
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function clearLastLine (stream) {
|
|
30
|
+
if (stream && typeof stream.moveCursor === 'function' && typeof stream.clearLine === 'function') {
|
|
31
|
+
stream.moveCursor(0, -1)
|
|
32
|
+
stream.clearLine(0)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (stream && typeof stream.write === 'function') {
|
|
37
|
+
stream.write('\x1B[1A\x1B[2K')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
async function select ({ message, choices }, context) {
|
|
30
42
|
const answer = await enquirer.prompt({
|
|
31
43
|
type: 'select',
|
|
@@ -38,6 +50,40 @@ async function select ({ message, choices }, context) {
|
|
|
38
50
|
return answer.value
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
async function password ({ message, prefix, separator }, context) {
|
|
54
|
+
const output = (context && context.output) || process.stderr
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const answer = await enquirer.prompt({
|
|
58
|
+
type: 'password',
|
|
59
|
+
name: 'value',
|
|
60
|
+
message,
|
|
61
|
+
symbols: {
|
|
62
|
+
prefix: {
|
|
63
|
+
pending: prefix,
|
|
64
|
+
submitted: prefix,
|
|
65
|
+
cancelled: prefix
|
|
66
|
+
},
|
|
67
|
+
separator: {
|
|
68
|
+
pending: separator,
|
|
69
|
+
submitted: separator,
|
|
70
|
+
cancelled: separator
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
...enquirerOptions(context)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
clearLastLine(output)
|
|
77
|
+
return answer.value
|
|
78
|
+
} catch (error) {
|
|
79
|
+
clearLastLine(output)
|
|
80
|
+
const e = new Error('prompt cancelled')
|
|
81
|
+
e.code = 'PROMPT_CANCELLED'
|
|
82
|
+
throw e
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
41
86
|
module.exports = {
|
|
87
|
+
password,
|
|
42
88
|
select
|
|
43
89
|
}
|
package/src/lib/main.js
CHANGED
|
@@ -130,7 +130,7 @@ const config = function (options = {}) {
|
|
|
130
130
|
if (readableFilepaths.length > 0) {
|
|
131
131
|
msg += ` from ${readableFilepaths.join(', ')}`
|
|
132
132
|
}
|
|
133
|
-
logger.
|
|
133
|
+
logger.success(`⟐ ${msg}`)
|
|
134
134
|
|
|
135
135
|
if (lastError) {
|
|
136
136
|
return { parsed: parsedAll, error: lastError }
|
|
@@ -8,7 +8,7 @@ const SAMPLE_ENV_KIT = require('../helpers/kits/sample')
|
|
|
8
8
|
const Errors = require('../helpers/errors')
|
|
9
9
|
const { determine } = require('./../helpers/envResolution')
|
|
10
10
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
11
|
-
const {
|
|
11
|
+
const { isDotenvPublicKey, isPlainKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
|
|
12
12
|
const keynames = require('../conventions/keynames')
|
|
13
13
|
const PostArmorUp = require('../api/postArmorUp')
|
|
14
14
|
const prompts = require('../helpers/prompts')
|
|
@@ -129,8 +129,7 @@ async function encryptTransform (options = {}) {
|
|
|
129
129
|
const { parsed } = scan(row.envSrc, { ik, ek })
|
|
130
130
|
|
|
131
131
|
for (const [key, values] of Object.entries(parsed)) {
|
|
132
|
-
|
|
133
|
-
if (isPublicKey(key)) {
|
|
132
|
+
if (isDotenvPublicKey(key) || isPlainKey(key)) {
|
|
134
133
|
continue
|
|
135
134
|
}
|
|
136
135
|
|
|
@@ -7,7 +7,7 @@ const TYPE_ENV_FILE = 'envFile'
|
|
|
7
7
|
const getResolver = require('./../resolvers/get')
|
|
8
8
|
const { determine } = require('./../helpers/envResolution')
|
|
9
9
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
10
|
-
const { mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
|
|
10
|
+
const { isPlainKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
|
|
11
11
|
const keynames = require('../conventions/keynames')
|
|
12
12
|
const Errors = require('../helpers/errors')
|
|
13
13
|
const PostArmorUp = require('../api/postArmorUp')
|
|
@@ -37,7 +37,7 @@ async function setTransform (options = {}) {
|
|
|
37
37
|
const fk = options.fk || '.env.keys'
|
|
38
38
|
let noArmor = options.noArmor // key storage selector below
|
|
39
39
|
const noCreate = options.noCreate
|
|
40
|
-
const noEncrypt = !options.encrypt
|
|
40
|
+
const noEncrypt = !options.encrypt || isPlainKey(key)
|
|
41
41
|
|
|
42
42
|
const processedEnvs = []
|
|
43
43
|
const changedFilepaths = []
|