@dotenvx/dotenvx 1.74.3 → 1.75.1
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 +24 -1
- package/README.md +70 -22
- package/package.json +2 -2
- package/src/cli/commands/ext.js +2 -2
- package/src/cli/dotenvx.js +29 -27
- package/src/cli/examples.js +8 -8
- package/src/db/device.js +3 -9
- package/src/lib/helpers/append.js +4 -3
- package/src/lib/helpers/cryptography/decryptKeyValue.js +10 -10
- package/src/lib/helpers/cryptography/encryptValue.js +2 -7
- package/src/lib/helpers/cryptography/index.js +1 -5
- package/src/lib/helpers/cryptography/localKeypair.js +6 -13
- package/src/lib/helpers/decryptDeviceValue.js +2 -5
- package/src/lib/helpers/encryptDeviceValue.js +2 -4
- package/src/lib/helpers/executeDynamic.js +1 -3
- package/src/lib/helpers/installPrecommitHook.js +9 -7
- package/src/lib/helpers/keyResolution/keyValues.js +2 -2
- package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +8 -3
- package/src/lib/helpers/keyResolution/keyValuesSync.js +2 -2
- package/src/lib/helpers/keyResolution/readFileKey.js +6 -4
- package/src/lib/helpers/keyResolution/readFileKeySync.js +6 -4
- package/src/lib/helpers/keypairMetadata.js +2 -2
- package/src/lib/helpers/parse.js +2 -2
- package/src/lib/helpers/readEnvKey.js +4 -3
- package/src/lib/helpers/replace.js +2 -2
- package/src/lib/services/armorPush.js +2 -2
- package/src/lib/services/decrypt.js +6 -7
- package/src/lib/services/encrypt.js +5 -6
- package/src/lib/services/genexample.js +16 -4
- package/src/lib/services/prebuild.js +5 -5
- package/src/lib/services/precommit.js +5 -5
- package/src/lib/services/rotate.js +4 -5
- package/src/lib/services/sets.js +11 -9
- package/src/lib/helpers/cryptography/isEncrypted.js +0 -7
- package/src/lib/helpers/dotenvParse.js +0 -55
- package/src/lib/helpers/isFullyEncrypted.js +0 -27
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,30 @@
|
|
|
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.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.75.1...main)
|
|
6
|
+
|
|
7
|
+
## [1.75.1](https://github.com/dotenvx/dotenvx/compare/v1.75.0...v1.75.1) (2026-06-20)
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* Publish as named binaries ([#851](https://github.com/dotenvx/dotenvx/pull/851))
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
* Remove references to `dotenvx-vlt` (now dotenvx-armor) ([#849](https://github.com/dotenvx/dotenvx/pull/849))
|
|
16
|
+
|
|
17
|
+
## [1.75.0](https://github.com/dotenvx/dotenvx/compare/v1.74.4...v1.75.0) (2026-06-19)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
* Swap in `@dotenvx/primitives` ([#848](https://github.com/dotenvx/dotenvx/pull/848))
|
|
22
|
+
* `ext` commands become first-class([#847](https://github.com/dotenvx/dotenvx/pull/847))
|
|
23
|
+
|
|
24
|
+
## [1.74.4](https://github.com/dotenvx/dotenvx/compare/v1.74.3...v1.74.4) (2026-06-19)
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
* Remove separately published `@dotenvx/next-env` from this repo ([#846](https://github.com/dotenvx/dotenvx/pull/846))
|
|
6
29
|
|
|
7
30
|
## [1.74.3](https://github.com/dotenvx/dotenvx/compare/v1.74.2...v1.74.3) (2026-06-19)
|
|
8
31
|
|
package/README.md
CHANGED
|
@@ -150,6 +150,47 @@ $ dotenvx run -- npx tsx index.ts
|
|
|
150
150
|
Hello Dotenvx
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
</details>
|
|
154
|
+
<details><summary>Next.js ▲</summary><br>
|
|
155
|
+
|
|
156
|
+
Install Dotenvx and `@dotenvx/next-env`.
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
$ npm install @dotenvx/dotenvx
|
|
160
|
+
$ npm install @dotenvx/next-env
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Override `@next/env` in your `package.json`.
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"overrides": {
|
|
168
|
+
"@next/env": "npm:@dotenvx/next-env"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Encrypt your `.env` file.
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
$ npx dotenvx encrypt
|
|
177
|
+
◈ encrypted (.env)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Your encrypted secrets are automatically injected and readable in Next.js.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
import { NextResponse } from 'next/server'
|
|
184
|
+
|
|
185
|
+
export async function GET() {
|
|
186
|
+
return NextResponse.json({
|
|
187
|
+
HELLO: process.env.HELLO
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Set `DOTENV_PRIVATE_KEY` in production before deploying.
|
|
193
|
+
|
|
153
194
|
</details>
|
|
154
195
|
<details><summary>Cloudflare Workers ⛅️</summary><br>
|
|
155
196
|
|
|
@@ -1613,7 +1654,7 @@ $ echo "HELLO=Dotenvx" > .env
|
|
|
1613
1654
|
|
|
1614
1655
|
$ dotenvx encrypt
|
|
1615
1656
|
◈ encrypted (.env) + local key (.env.keys)
|
|
1616
|
-
⮕ next run [dotenvx
|
|
1657
|
+
⮕ next run [dotenvx gitignore --pattern .env.keys] to gitignore .env.keys
|
|
1617
1658
|
⮕ next run [DOTENV_PRIVATE_KEY='122...0b8' dotenvx run -- yourcommand] to test decryption locally
|
|
1618
1659
|
```
|
|
1619
1660
|
|
|
@@ -1628,7 +1669,7 @@ $ echo "HELLO=Production" > .env.production
|
|
|
1628
1669
|
|
|
1629
1670
|
$ dotenvx encrypt -f .env.production
|
|
1630
1671
|
◈ encrypted (.env.production) + local key (.env.keys)
|
|
1631
|
-
⮕ next run [dotenvx
|
|
1672
|
+
⮕ next run [dotenvx gitignore --pattern .env.keys] to gitignore .env.keys
|
|
1632
1673
|
⮕ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locally
|
|
1633
1674
|
```
|
|
1634
1675
|
|
|
@@ -2121,12 +2162,19 @@ Commands:
|
|
|
2121
2162
|
set <KEY> <value> set a single environment variable
|
|
2122
2163
|
encrypt encrypt .env file(s)
|
|
2123
2164
|
decrypt decrypt .env file(s)
|
|
2165
|
+
rotate rotate keypair(s) and re-encrypt .env file(s)
|
|
2124
2166
|
keypair [KEY] print public/private keys for .env file(s)
|
|
2125
2167
|
ls [directory] print all .env files in a tree structure
|
|
2168
|
+
gitignore append to .gitignore
|
|
2169
|
+
precommit [directory]
|
|
2170
|
+
prevent committing .env files to code
|
|
2171
|
+
prebuild [directory]
|
|
2172
|
+
prevent including .env files in docker
|
|
2126
2173
|
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2174
|
+
Professional Security:
|
|
2175
|
+
login log in to move keys off-device, share with your team, and audit access
|
|
2176
|
+
logout log out of connected security features
|
|
2177
|
+
armor ⛨ move private keys off-device [www.dotenvx.com/armor]
|
|
2130
2178
|
```
|
|
2131
2179
|
|
|
2132
2180
|
You can get more detailed help per command with `dotenvx help COMMAND`.
|
|
@@ -2174,9 +2222,9 @@ X.X.X
|
|
|
2174
2222
|
|
|
2175
2223
|
</details>
|
|
2176
2224
|
|
|
2177
|
-
###
|
|
2225
|
+
### Utilities
|
|
2178
2226
|
|
|
2179
|
-
|
|
2227
|
+
Additional workflow helpers.
|
|
2180
2228
|
|
|
2181
2229
|
<details><summary>`ext genexample`</summary><br>
|
|
2182
2230
|
|
|
@@ -2233,47 +2281,47 @@ HELLO=""
|
|
|
2233
2281
|
```
|
|
2234
2282
|
|
|
2235
2283
|
</details>
|
|
2236
|
-
<details><summary>`
|
|
2284
|
+
<details><summary>`gitignore`</summary><br>
|
|
2237
2285
|
|
|
2238
2286
|
Gitignore your `.env` files.
|
|
2239
2287
|
|
|
2240
2288
|
```sh
|
|
2241
|
-
$ dotenvx
|
|
2289
|
+
$ dotenvx gitignore
|
|
2242
2290
|
▣ ignored .env* (.gitignore)
|
|
2243
2291
|
```
|
|
2244
2292
|
|
|
2245
2293
|
</details>
|
|
2246
|
-
<details><summary>`
|
|
2294
|
+
<details><summary>`gitignore --pattern`</summary><br>
|
|
2247
2295
|
|
|
2248
2296
|
Gitignore specific pattern(s) of `.env` files.
|
|
2249
2297
|
|
|
2250
2298
|
```sh
|
|
2251
|
-
$ dotenvx
|
|
2299
|
+
$ dotenvx gitignore --pattern .env.keys
|
|
2252
2300
|
▣ ignored .env.keys (.gitignore)
|
|
2253
2301
|
```
|
|
2254
2302
|
|
|
2255
2303
|
</details>
|
|
2256
|
-
<details><summary>`
|
|
2304
|
+
<details><summary>`precommit`</summary><br>
|
|
2257
2305
|
|
|
2258
2306
|
Prevent `.env` files from being committed to code.
|
|
2259
2307
|
|
|
2260
2308
|
```sh
|
|
2261
|
-
$ dotenvx
|
|
2309
|
+
$ dotenvx precommit
|
|
2262
2310
|
▣ .env files (1) protected (encrypted or gitignored)
|
|
2263
2311
|
```
|
|
2264
2312
|
|
|
2265
2313
|
</details>
|
|
2266
|
-
<details><summary>`
|
|
2314
|
+
<details><summary>`precommit --install`</summary><br>
|
|
2267
2315
|
|
|
2268
2316
|
Install a shell script to `.git/hooks/pre-commit` to prevent accidentally committing any `.env` files to source control.
|
|
2269
2317
|
|
|
2270
2318
|
```sh
|
|
2271
|
-
$ dotenvx
|
|
2272
|
-
▣ dotenvx
|
|
2319
|
+
$ dotenvx precommit --install
|
|
2320
|
+
▣ dotenvx precommit installed [.git/hooks/pre-commit]
|
|
2273
2321
|
```
|
|
2274
2322
|
|
|
2275
2323
|
</details>
|
|
2276
|
-
<details><summary>`
|
|
2324
|
+
<details><summary>`precommit directory`</summary><br>
|
|
2277
2325
|
|
|
2278
2326
|
Prevent `.env` files from being committed to code inside a specified path to a directory.
|
|
2279
2327
|
|
|
@@ -2282,12 +2330,12 @@ $ echo "HELLO=Dotenvx" > .env
|
|
|
2282
2330
|
$ mkdir -p apps/backend
|
|
2283
2331
|
$ echo "HELLO=Backend" > apps/backend/.env
|
|
2284
2332
|
|
|
2285
|
-
$ dotenvx
|
|
2333
|
+
$ dotenvx precommit apps/backend
|
|
2286
2334
|
▣ apps/backend/.env not protected (encrypted or gitignored)
|
|
2287
2335
|
```
|
|
2288
2336
|
|
|
2289
2337
|
</details>
|
|
2290
|
-
<details><summary>`
|
|
2338
|
+
<details><summary>`prebuild`</summary><br>
|
|
2291
2339
|
|
|
2292
2340
|
Prevent `.env` files from being built into your docker containers.
|
|
2293
2341
|
|
|
@@ -2302,12 +2350,12 @@ COPY --from=dotenv/dotenvx:latest /usr/local/bin/dotenvx /bin/local/bin
|
|
|
2302
2350
|
|
|
2303
2351
|
# ... orther container commands
|
|
2304
2352
|
|
|
2305
|
-
RUN dotenvx
|
|
2353
|
+
RUN dotenvx prebuild
|
|
2306
2354
|
CMD ["/usr/local/bin/dotenvx", "run", "--", "node", "index.js"]
|
|
2307
2355
|
```
|
|
2308
2356
|
|
|
2309
2357
|
</details>
|
|
2310
|
-
<details><summary>`
|
|
2358
|
+
<details><summary>`prebuild directory`</summary><br>
|
|
2311
2359
|
|
|
2312
2360
|
Prevent `.env` files from being built into your docker containers inside a specified path to a directory.
|
|
2313
2361
|
|
|
@@ -2322,7 +2370,7 @@ COPY --from=dotenv/dotenvx:latest /usr/local/bin/dotenvx /bin/local/bin
|
|
|
2322
2370
|
|
|
2323
2371
|
# ... orther container commands
|
|
2324
2372
|
|
|
2325
|
-
RUN dotenvx
|
|
2373
|
+
RUN dotenvx prebuild apps/backend
|
|
2326
2374
|
CMD ["/usr/local/bin/dotenvx", "run", "--", "node", "apps/backend/index.js"]
|
|
2327
2375
|
```
|
|
2328
2376
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.75.1",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a secure dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
},
|
|
44
44
|
"funding": "https://dotenvx.com",
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@dotenvx/primitives": "^0.8.0",
|
|
46
47
|
"commander": "^11.1.0",
|
|
47
48
|
"conf": "^10.2.0",
|
|
48
49
|
"dotenv": "^17.2.1",
|
|
49
|
-
"eciesjs": "^0.4.10",
|
|
50
50
|
"enquirer": "^2.4.1",
|
|
51
51
|
"env-paths": "^2.2.1",
|
|
52
52
|
"execa": "^5.1.1",
|
package/src/cli/commands/ext.js
CHANGED
|
@@ -39,7 +39,7 @@ ext.command('genexample')
|
|
|
39
39
|
|
|
40
40
|
// dotenvx ext gitignore
|
|
41
41
|
ext.command('gitignore')
|
|
42
|
-
.description('append to .gitignore
|
|
42
|
+
.description('append to .gitignore')
|
|
43
43
|
.addHelpText('after', examples.gitignore)
|
|
44
44
|
.option('--pattern <patterns...>', 'pattern(s) to gitignore', ['.env*'])
|
|
45
45
|
.action(function (...args) {
|
|
@@ -48,7 +48,7 @@ ext.command('gitignore')
|
|
|
48
48
|
|
|
49
49
|
// dotenvx ext prebuild
|
|
50
50
|
ext.command('prebuild')
|
|
51
|
-
.description('prevent including .env files in docker
|
|
51
|
+
.description('prevent including .env files in docker')
|
|
52
52
|
.addHelpText('after', examples.prebuild)
|
|
53
53
|
.argument('[directory]', 'directory to prevent including .env files from', '.')
|
|
54
54
|
.action(function (...args) {
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -194,6 +194,34 @@ program.command('ls')
|
|
|
194
194
|
return require('./actions/ls').apply(this, args)
|
|
195
195
|
})
|
|
196
196
|
|
|
197
|
+
// dotenvx gitignore
|
|
198
|
+
program.command('gitignore')
|
|
199
|
+
.description('append to .gitignore')
|
|
200
|
+
.addHelpText('after', examples.gitignore)
|
|
201
|
+
.option('--pattern <patterns...>', 'pattern(s) to gitignore', ['.env*'])
|
|
202
|
+
.action(function (...args) {
|
|
203
|
+
return require('./actions/ext/gitignore').apply(this, args)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// dotenvx precommit
|
|
207
|
+
program.command('precommit')
|
|
208
|
+
.description('prevent committing .env files to code')
|
|
209
|
+
.addHelpText('after', examples.precommit)
|
|
210
|
+
.argument('[directory]', 'directory to prevent committing .env files from', '.')
|
|
211
|
+
.option('-i, --install', 'install to .git/hooks/pre-commit')
|
|
212
|
+
.action(function (...args) {
|
|
213
|
+
return require('./actions/ext/precommit').apply(this, args)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// dotenvx prebuild
|
|
217
|
+
program.command('prebuild')
|
|
218
|
+
.description('prevent including .env files in docker')
|
|
219
|
+
.addHelpText('after', examples.prebuild)
|
|
220
|
+
.argument('[directory]', 'directory to prevent including .env files from', '.')
|
|
221
|
+
.action(function (...args) {
|
|
222
|
+
return require('./actions/ext/prebuild').apply(this, args)
|
|
223
|
+
})
|
|
224
|
+
|
|
197
225
|
// dotenvx doctor
|
|
198
226
|
program.command('doctor', { hidden: true })
|
|
199
227
|
.description('scan for dotenv loaders')
|
|
@@ -242,7 +270,6 @@ program.addHelpText('after', 'Professional Security: ')
|
|
|
242
270
|
program.addHelpText('after', ' login log in to move keys off-device, share with your team, and audit access')
|
|
243
271
|
program.addHelpText('after', ' logout log out of connected security features')
|
|
244
272
|
program.addHelpText('after', ' armor ⛨ move private keys off-device [www.dotenvx.com/armor]')
|
|
245
|
-
program.addHelpText('after', ' ext ⊕ extensions')
|
|
246
273
|
|
|
247
274
|
// dotenvx armor
|
|
248
275
|
require('./commands/armor')(program.command('armor', { hidden: true }))
|
|
@@ -250,30 +277,6 @@ require('./commands/armor')(program.command('armor', { hidden: true }))
|
|
|
250
277
|
// dotenvx ext
|
|
251
278
|
program.addCommand(require('./commands/ext'))
|
|
252
279
|
|
|
253
|
-
//
|
|
254
|
-
// MOVED
|
|
255
|
-
//
|
|
256
|
-
program.command('prebuild')
|
|
257
|
-
.description('DEPRECATED: moved to [dotenvx ext prebuild]')
|
|
258
|
-
.addHelpText('after', examples.prebuild)
|
|
259
|
-
.argument('[directory]', 'directory to prevent including .env files from', '.')
|
|
260
|
-
.action(function (...args) {
|
|
261
|
-
logger.warn('DEPRECATION NOTICE: [prebuild] has moved to [dotenvx ext prebuild]')
|
|
262
|
-
|
|
263
|
-
require('./actions/ext/prebuild').apply(this, args)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
program.command('precommit')
|
|
267
|
-
.description('DEPRECATED: moved to [dotenvx ext precommit]')
|
|
268
|
-
.addHelpText('after', examples.precommit)
|
|
269
|
-
.argument('[directory]', 'directory to prevent committing .env files from', '.')
|
|
270
|
-
.option('-i, --install', 'install to .git/hooks/pre-commit')
|
|
271
|
-
.action(function (...args) {
|
|
272
|
-
logger.warn('DEPRECATION NOTICE: [precommit] has moved to [dotenvx ext precommit]')
|
|
273
|
-
|
|
274
|
-
require('./actions/ext/precommit').apply(this, args)
|
|
275
|
-
})
|
|
276
|
-
|
|
277
280
|
// override helpInformation to hide DEPRECATED and 'ext' commands
|
|
278
281
|
program.helpInformation = function () {
|
|
279
282
|
const originalHelp = Command.prototype.helpInformation.call(this)
|
|
@@ -286,8 +289,7 @@ program.helpInformation = function () {
|
|
|
286
289
|
const filteredLines = lines.filter(line =>
|
|
287
290
|
!line.includes('DEPRECATED') &&
|
|
288
291
|
!line.includes('help [command]') &&
|
|
289
|
-
!line.includes('🔌 extensions')
|
|
290
|
-
!/^\s*ls\b/.test(line)
|
|
292
|
+
!line.includes('🔌 extensions')
|
|
291
293
|
)
|
|
292
294
|
|
|
293
295
|
return filteredLines.join('\n')
|
package/src/cli/examples.js
CHANGED
|
@@ -27,14 +27,14 @@ const precommit = function () {
|
|
|
27
27
|
Examples:
|
|
28
28
|
|
|
29
29
|
\`\`\`
|
|
30
|
-
$ dotenvx
|
|
31
|
-
$ dotenvx
|
|
30
|
+
$ dotenvx precommit
|
|
31
|
+
$ dotenvx precommit --install
|
|
32
32
|
\`\`\`
|
|
33
33
|
|
|
34
34
|
Try it:
|
|
35
35
|
|
|
36
36
|
\`\`\`
|
|
37
|
-
$ dotenvx
|
|
37
|
+
$ dotenvx precommit
|
|
38
38
|
[dotenvx@0.45.0][precommit] success
|
|
39
39
|
\`\`\`
|
|
40
40
|
`
|
|
@@ -45,13 +45,13 @@ const prebuild = function () {
|
|
|
45
45
|
Examples:
|
|
46
46
|
|
|
47
47
|
\`\`\`
|
|
48
|
-
$ dotenvx
|
|
48
|
+
$ dotenvx prebuild
|
|
49
49
|
\`\`\`
|
|
50
50
|
|
|
51
51
|
Try it:
|
|
52
52
|
|
|
53
53
|
\`\`\`
|
|
54
|
-
$ dotenvx
|
|
54
|
+
$ dotenvx prebuild
|
|
55
55
|
[dotenvx@0.10.0][prebuild] success
|
|
56
56
|
\`\`\`
|
|
57
57
|
`
|
|
@@ -62,14 +62,14 @@ const gitignore = function () {
|
|
|
62
62
|
Examples:
|
|
63
63
|
|
|
64
64
|
\`\`\`
|
|
65
|
-
$ dotenvx
|
|
66
|
-
$ dotenvx
|
|
65
|
+
$ dotenvx gitignore
|
|
66
|
+
$ dotenvx gitignore --pattern .env.keys
|
|
67
67
|
\`\`\`
|
|
68
68
|
|
|
69
69
|
Try it:
|
|
70
70
|
|
|
71
71
|
\`\`\`
|
|
72
|
-
$ dotenvx
|
|
72
|
+
$ dotenvx gitignore
|
|
73
73
|
▣ ignored .env* (.gitignore)
|
|
74
74
|
\`\`\`
|
|
75
75
|
`
|
package/src/db/device.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const Conf = require('conf')
|
|
2
|
-
const {
|
|
2
|
+
const { derive, keypair } = require('@dotenvx/primitives')
|
|
3
3
|
|
|
4
4
|
const encryptDeviceValue = require('./../lib/helpers/encryptDeviceValue')
|
|
5
5
|
const decryptDeviceValue = require('./../lib/helpers/decryptDeviceValue')
|
|
@@ -38,9 +38,7 @@ class Device {
|
|
|
38
38
|
return currentPrivateKey
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
const kp = new PrivateKey()
|
|
43
|
-
const _privateKey = kp.secret.toString('hex')
|
|
41
|
+
const _privateKey = keypair().privateKey
|
|
44
42
|
|
|
45
43
|
this.store.set('private_key/1', _privateKey)
|
|
46
44
|
|
|
@@ -54,11 +52,7 @@ class Device {
|
|
|
54
52
|
return ''
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
const _privateKey = new PrivateKey(Buffer.from(privateKeyHex, 'hex'))
|
|
59
|
-
|
|
60
|
-
// compute publicKey from privateKey
|
|
61
|
-
return _privateKey.publicKey.toHex()
|
|
55
|
+
return derive(privateKeyHex)
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
encrypt (value) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const quotes = require('./quotes')
|
|
2
|
-
const
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
3
3
|
const escapeForRegex = require('./escapeForRegex')
|
|
4
4
|
const escapeDollarSigns = require('./escapeDollarSigns')
|
|
5
5
|
|
|
@@ -7,11 +7,12 @@ function append (src, key, appendValue) {
|
|
|
7
7
|
let output
|
|
8
8
|
let newPart = ''
|
|
9
9
|
|
|
10
|
-
const parsed =
|
|
10
|
+
const { parsed } = scan(src, { expandDoubleQuotedNewlines: false, convertWindowsNewlines: false })
|
|
11
11
|
const _quotes = quotes(src)
|
|
12
12
|
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
13
13
|
const quote = _quotes[key]
|
|
14
|
-
const
|
|
14
|
+
const values = parsed[key]
|
|
15
|
+
const originalValue = values[values.length - 1]
|
|
15
16
|
|
|
16
17
|
newPart += `${key}=${quote}${originalValue},${appendValue}${quote}`
|
|
17
18
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
const { decrypt } = require('
|
|
1
|
+
const { decrypt } = require('@dotenvx/primitives')
|
|
2
2
|
|
|
3
3
|
const Errors = require('./../errors')
|
|
4
4
|
|
|
5
5
|
const PREFIX = 'encrypted:'
|
|
6
6
|
|
|
7
|
+
function primitiveMessage (error) {
|
|
8
|
+
return error.message.replace(/^\[[A-Z_]+\] /, '')
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
function decryptKeyValue (key, value, privateKeyName, privateKey) {
|
|
8
12
|
let decryptedValue
|
|
9
13
|
let decryptionError
|
|
@@ -18,23 +22,19 @@ function decryptKeyValue (key, value, privateKeyName, privateKey) {
|
|
|
18
22
|
} else {
|
|
19
23
|
const privateKeys = privateKey.split(',')
|
|
20
24
|
for (const privKey of privateKeys) {
|
|
21
|
-
const secret = Buffer.from(privKey, 'hex')
|
|
22
|
-
const encoded = value.substring(PREFIX.length)
|
|
23
|
-
const ciphertext = Buffer.from(encoded, 'base64')
|
|
24
|
-
|
|
25
25
|
try {
|
|
26
|
-
decryptedValue = decrypt(
|
|
26
|
+
decryptedValue = decrypt(privKey, value)
|
|
27
27
|
decryptionError = null // reset to null error (scenario for multiple private keys)
|
|
28
28
|
break
|
|
29
29
|
} catch (e) {
|
|
30
|
-
if (e.
|
|
30
|
+
if (e.code === 'INVALID_PRIVATE_KEY') {
|
|
31
31
|
decryptionError = new Errors({ key, privateKeyName, privateKey }).invalidPrivateKey()
|
|
32
|
-
} else if (e.
|
|
32
|
+
} else if (e.code === 'WRONG_PRIVATE_KEY') {
|
|
33
33
|
decryptionError = new Errors({ key, privateKeyName, privateKey }).wrongPrivateKey()
|
|
34
|
-
} else if (e.
|
|
34
|
+
} else if (e.code === 'MALFORMED_ENCRYPTED_DATA' || primitiveMessage(e).startsWith('bad point:')) {
|
|
35
35
|
decryptionError = new Errors({ key, privateKeyName, privateKey }).malformedEncryptedData()
|
|
36
36
|
} else {
|
|
37
|
-
decryptionError = new Errors({ key, privateKeyName, privateKey, message: e
|
|
37
|
+
decryptionError = new Errors({ key, privateKeyName, privateKey, message: primitiveMessage(e) }).decryptionFailed()
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
const { encrypt } = require('
|
|
2
|
-
|
|
3
|
-
const PREFIX = 'encrypted:'
|
|
1
|
+
const { encrypt } = require('@dotenvx/primitives')
|
|
4
2
|
|
|
5
3
|
function encryptValue (value, publicKey) {
|
|
6
|
-
|
|
7
|
-
const encoded = Buffer.from(ciphertext, 'hex').toString('base64') // base64 encode ciphertext
|
|
8
|
-
|
|
9
|
-
return `${PREFIX}${encoded}`
|
|
4
|
+
return encrypt(publicKey, value)
|
|
10
5
|
}
|
|
11
6
|
|
|
12
7
|
module.exports = encryptValue
|
|
@@ -4,15 +4,11 @@ module.exports = {
|
|
|
4
4
|
localKeypair: require('./localKeypair'),
|
|
5
5
|
encryptValue: require('./encryptValue'),
|
|
6
6
|
decryptKeyValue: require('./decryptKeyValue'),
|
|
7
|
-
isEncrypted: require('./isEncrypted'),
|
|
8
7
|
isPublicKey: require('./isPublicKey'),
|
|
9
8
|
mutateKeysSrc: require('./mutateKeysSrc'),
|
|
10
9
|
mutateKeysSrcSync: require('./mutateKeysSrcSync'),
|
|
11
10
|
provision: require('./provision'),
|
|
12
11
|
provisionSync: require('./provisionSync'),
|
|
13
12
|
provisionWithPrivateKey: require('./provisionWithPrivateKey'),
|
|
14
|
-
mutateSrc: require('./mutateSrc')
|
|
15
|
-
|
|
16
|
-
// other
|
|
17
|
-
isFullyEncrypted: require('./../isFullyEncrypted')
|
|
13
|
+
mutateSrc: require('./mutateSrc')
|
|
18
14
|
}
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { derive, keypair } = require('@dotenvx/primitives')
|
|
2
2
|
|
|
3
3
|
function localKeypair (existingPrivateKey) {
|
|
4
|
-
let kp
|
|
5
|
-
|
|
6
4
|
if (existingPrivateKey) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
return {
|
|
6
|
+
publicKey: derive(existingPrivateKey),
|
|
7
|
+
privateKey: existingPrivateKey
|
|
8
|
+
}
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
const privateKey = kp.secret.toString('hex')
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
publicKey,
|
|
17
|
-
privateKey
|
|
18
|
-
}
|
|
11
|
+
return keypair()
|
|
19
12
|
}
|
|
20
13
|
|
|
21
14
|
module.exports = localKeypair
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
const { decrypt } = require('
|
|
1
|
+
const { decrypt } = require('@dotenvx/primitives')
|
|
2
2
|
|
|
3
3
|
function decryptDeviceValue (value, privateKey) {
|
|
4
|
-
|
|
5
|
-
const ciphertext = Buffer.from(value, 'base64')
|
|
6
|
-
|
|
7
|
-
return decrypt(secret, ciphertext).toString()
|
|
4
|
+
return decrypt(privateKey, value, { prefix: false })
|
|
8
5
|
}
|
|
9
6
|
|
|
10
7
|
module.exports = decryptDeviceValue
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
const { encrypt } = require('
|
|
1
|
+
const { encrypt } = require('@dotenvx/primitives')
|
|
2
2
|
|
|
3
3
|
function encryptDeviceValue (value, publicKey) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return Buffer.from(ciphertext, 'hex').toString('base64')
|
|
4
|
+
return encrypt(publicKey, value, { prefix: false })
|
|
7
5
|
}
|
|
8
6
|
|
|
9
7
|
module.exports = encryptDeviceValue
|
|
@@ -45,7 +45,6 @@ function dynamicAttempts (command, forwardedArgs) {
|
|
|
45
45
|
if (command === 'vlt') {
|
|
46
46
|
return [
|
|
47
47
|
['dotenvx-armor', forwardedArgs],
|
|
48
|
-
['dotenvx-vlt', forwardedArgs],
|
|
49
48
|
['dotenvx-ops', forwardedArgs]
|
|
50
49
|
]
|
|
51
50
|
}
|
|
@@ -53,8 +52,7 @@ function dynamicAttempts (command, forwardedArgs) {
|
|
|
53
52
|
if (command === 'ops') {
|
|
54
53
|
return [
|
|
55
54
|
['dotenvx-armor', forwardedArgs],
|
|
56
|
-
['dotenvx-ops', forwardedArgs]
|
|
57
|
-
['dotenvx-vlt', forwardedArgs]
|
|
55
|
+
['dotenvx-ops', forwardedArgs]
|
|
58
56
|
]
|
|
59
57
|
}
|
|
60
58
|
|
|
@@ -6,10 +6,10 @@ const HOOK_SCRIPT = `#!/bin/sh
|
|
|
6
6
|
|
|
7
7
|
if command -v dotenvx 2>&1 >/dev/null
|
|
8
8
|
then
|
|
9
|
-
dotenvx
|
|
9
|
+
dotenvx precommit
|
|
10
10
|
elif npx dotenvx -V >/dev/null 2>&1
|
|
11
11
|
then
|
|
12
|
-
npx dotenvx
|
|
12
|
+
npx dotenvx precommit
|
|
13
13
|
else
|
|
14
14
|
if [ -t 2 ]; then
|
|
15
15
|
RED="$(printf '\\033[31m')"
|
|
@@ -19,7 +19,7 @@ else
|
|
|
19
19
|
RESET=""
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
-
printf "%s☠ 'dotenvx
|
|
22
|
+
printf "%s☠ 'dotenvx precommit' command not found (.git/hooks/precommit) fix: [curl -sfS https://dotenvx.sh | sh]%s\n" "$RED" "$RESET" >&2
|
|
23
23
|
exit 1
|
|
24
24
|
fi
|
|
25
25
|
`
|
|
@@ -35,17 +35,19 @@ class InstallPrecommitHook {
|
|
|
35
35
|
try {
|
|
36
36
|
// Check if the pre-commit file already exists
|
|
37
37
|
if (this._exists()) {
|
|
38
|
+
const currentHook = this._currentHook()
|
|
39
|
+
|
|
38
40
|
// Check if 'dotenvx precommit' already exists in the file
|
|
39
|
-
if (
|
|
41
|
+
if (currentHook.includes('dotenvx precommit') || currentHook.includes('dotenvx ext precommit')) {
|
|
40
42
|
// do nothing
|
|
41
|
-
successMessage = `▣ dotenvx
|
|
43
|
+
successMessage = `▣ dotenvx precommit exists [${this.hookPath}]`
|
|
42
44
|
} else {
|
|
43
45
|
this._appendHook()
|
|
44
|
-
successMessage = `▣ dotenvx
|
|
46
|
+
successMessage = `▣ dotenvx precommit appended [${this.hookPath}]`
|
|
45
47
|
}
|
|
46
48
|
} else {
|
|
47
49
|
this._createHook()
|
|
48
|
-
successMessage = `▣ dotenvx
|
|
50
|
+
successMessage = `▣ dotenvx precommit installed [${this.hookPath}]`
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
2
3
|
|
|
3
4
|
const fsx = require('./../fsx')
|
|
4
|
-
const dotenvParse = require('./../dotenvParse')
|
|
5
5
|
const keyNamesForEnvFile = require('./keyNamesForEnvFile')
|
|
6
6
|
const readProcessKey = require('./readProcessKey')
|
|
7
7
|
const readFileKey = require('./readFileKey')
|
|
@@ -16,7 +16,7 @@ async function invertForPrivateKeyName (filepath) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const envSrc = await fsx.readFileX(filepath)
|
|
19
|
-
const envParsed =
|
|
19
|
+
const { parsed: envParsed } = scan(envSrc)
|
|
20
20
|
|
|
21
21
|
let publicKeyName
|
|
22
22
|
for (const keyName of Object.keys(envParsed)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
2
3
|
|
|
3
|
-
const dotenvParse = require('./../dotenvParse')
|
|
4
4
|
const readFileKeySync = require('./readFileKeySync')
|
|
5
5
|
const armorKeypairSync = require('../cryptography/armorKeypairSync')
|
|
6
6
|
|
|
@@ -23,14 +23,19 @@ function publicKeyNameFromEnvSrc (envParsed) {
|
|
|
23
23
|
return null
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function finalValue (parsed, keyName) {
|
|
27
|
+
const values = parsed[keyName]
|
|
28
|
+
return values ? values[values.length - 1] : undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
function keyValuesFromEnvSrc (src, privateKeyName = null, opts = {}) {
|
|
27
32
|
let keysFilepath = opts.keysFilepath || null
|
|
28
33
|
const noArmor = opts.noArmor === true
|
|
29
34
|
const processEnv = opts.processEnv || process.env
|
|
30
|
-
const envParsed =
|
|
35
|
+
const { parsed: envParsed } = scan(src)
|
|
31
36
|
|
|
32
37
|
const publicKeyName = publicKeyNameFromEnvSrc(envParsed)
|
|
33
|
-
const publicKeyValue = publicKeyName ? readProcessKey(processEnv, publicKeyName) || envParsed
|
|
38
|
+
const publicKeyValue = publicKeyName ? readProcessKey(processEnv, publicKeyName) || finalValue(envParsed, publicKeyName) || null : null
|
|
34
39
|
|
|
35
40
|
if (!privateKeyName && publicKeyName) {
|
|
36
41
|
privateKeyName = publicKeyName.replace(PUBLIC_KEY_SCHEMA, PRIVATE_KEY_SCHEMA)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
2
3
|
|
|
3
4
|
const fsx = require('./../fsx')
|
|
4
|
-
const dotenvParse = require('./../dotenvParse')
|
|
5
5
|
const keyNamesForEnvFile = require('./keyNamesForEnvFile')
|
|
6
6
|
const readProcessKey = require('./readProcessKey')
|
|
7
7
|
const readFileKeySync = require('./readFileKeySync')
|
|
@@ -16,7 +16,7 @@ function invertForPrivateKeyName (filepath) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const envSrc = fsx.readFileXSync(filepath)
|
|
19
|
-
const envParsed =
|
|
19
|
+
const { parsed: envParsed } = scan(envSrc)
|
|
20
20
|
|
|
21
21
|
let publicKeyName
|
|
22
22
|
for (const keyName of Object.keys(envParsed)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fsx = require('./../fsx')
|
|
2
|
-
const
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
3
3
|
|
|
4
4
|
async function readFileKey (keyName, filepath) {
|
|
5
5
|
if (!(await fsx.exists(filepath))) {
|
|
@@ -7,10 +7,12 @@ async function readFileKey (keyName, filepath) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const src = await fsx.readFileX(filepath)
|
|
10
|
-
const parsed =
|
|
10
|
+
const { parsed } = scan(src)
|
|
11
|
+
const values = parsed[keyName]
|
|
11
12
|
|
|
12
|
-
if (
|
|
13
|
-
|
|
13
|
+
if (values && values.length > 0) {
|
|
14
|
+
const value = values[values.length - 1]
|
|
15
|
+
return value || undefined
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
const fsx = require('./../fsx')
|
|
2
|
-
const
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
3
3
|
|
|
4
4
|
function readFileKeySync (keyName, filepath) {
|
|
5
5
|
if (fsx.existsSync(filepath)) {
|
|
6
6
|
const src = fsx.readFileXSync(filepath)
|
|
7
|
-
const parsed =
|
|
7
|
+
const { parsed } = scan(src)
|
|
8
|
+
const values = parsed[keyName]
|
|
8
9
|
|
|
9
|
-
if (
|
|
10
|
-
|
|
10
|
+
if (values && values.length > 0) {
|
|
11
|
+
const value = values[values.length - 1]
|
|
12
|
+
return value || undefined
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
const execa = require('execa')
|
|
4
|
-
const
|
|
4
|
+
const { scan } = require('@dotenvx/primitives')
|
|
5
5
|
const canonicalEnvFilename = require('./canonicalEnvFilename')
|
|
6
6
|
const environment = require('./envResolution/environment')
|
|
7
7
|
const sanitizeCommandForMetadata = require('./sanitizeCommandForMetadata')
|
|
@@ -56,7 +56,7 @@ function envKeys (envFile) {
|
|
|
56
56
|
return null
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const parsed =
|
|
59
|
+
const { parsed } = scan(src)
|
|
60
60
|
|
|
61
61
|
if (!parsed || Object.keys(parsed).length === 0) return null
|
|
62
62
|
|
package/src/lib/helpers/parse.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const decryptKeyValue = require('./cryptography/decryptKeyValue')
|
|
2
|
-
const
|
|
2
|
+
const { encrypted } = require('@dotenvx/primitives')
|
|
3
3
|
const evalKeyValue = require('./evalKeyValue')
|
|
4
4
|
const resolveEscapeSequences = require('./resolveEscapeSequences')
|
|
5
5
|
|
|
@@ -45,7 +45,7 @@ class Parse {
|
|
|
45
45
|
this.errors.push(e)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const encryptedPrefixed =
|
|
48
|
+
const encryptedPrefixed = encrypted(this.parsed[key]) // do not eval encrypted prefix
|
|
49
49
|
|
|
50
50
|
// eval empty, double, or backticks
|
|
51
51
|
let evaled = false
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const
|
|
2
|
+
const { scan } = require('@dotenvx/primitives')
|
|
3
3
|
const Errors = require('./errors')
|
|
4
4
|
|
|
5
5
|
function ignored (error, options) {
|
|
@@ -17,8 +17,9 @@ function readEnvKey (key, filepath, options = {}) {
|
|
|
17
17
|
return undefined
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const parsed =
|
|
21
|
-
const
|
|
20
|
+
const { parsed } = scan(src)
|
|
21
|
+
const values = parsed[key]
|
|
22
|
+
const value = values ? values[values.length - 1] : undefined
|
|
22
23
|
if (value === undefined) {
|
|
23
24
|
const error = new Errors({ key }).missingKey()
|
|
24
25
|
if (ignored(error, options)) return undefined
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { scan } = require('@dotenvx/primitives')
|
|
2
2
|
const escapeForRegex = require('./escapeForRegex')
|
|
3
3
|
|
|
4
4
|
function replaceExistingValue (src, key, originalValue, replaceValue) {
|
|
@@ -45,7 +45,7 @@ function replace (src, key, replaceValue) {
|
|
|
45
45
|
let output
|
|
46
46
|
let newPart = ''
|
|
47
47
|
|
|
48
|
-
const parsed =
|
|
48
|
+
const { parsed } = scan(src, { expandDoubleQuotedNewlines: false, convertWindowsNewlines: false })
|
|
49
49
|
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
|
|
50
50
|
const allValues = parsed[key]
|
|
51
51
|
let duplicateOutput = src
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { derive } = require('@dotenvx/primitives')
|
|
2
2
|
const prompts = require('../helpers/prompts')
|
|
3
3
|
const PostArmorPush = require('../api/postArmorPush')
|
|
4
4
|
const keyNamesForEnvFile = require('../helpers/keyResolution/keyNamesForEnvFile')
|
|
@@ -7,7 +7,7 @@ const teamChoicesFromMeta = require('../helpers/teamChoicesFromMeta')
|
|
|
7
7
|
|
|
8
8
|
function publicKeyFromPrivateKey (privateKey) {
|
|
9
9
|
try {
|
|
10
|
-
return
|
|
10
|
+
return derive(privateKey)
|
|
11
11
|
} catch {
|
|
12
12
|
return ''
|
|
13
13
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const picomatch = require('picomatch')
|
|
4
|
+
const { encrypted, scan } = require('@dotenvx/primitives')
|
|
4
5
|
|
|
5
6
|
const TYPE_ENV_FILE = 'envFile'
|
|
6
7
|
|
|
@@ -16,12 +17,10 @@ const {
|
|
|
16
17
|
} = require('./../helpers/keyResolution')
|
|
17
18
|
|
|
18
19
|
const {
|
|
19
|
-
decryptKeyValue
|
|
20
|
-
isEncrypted
|
|
20
|
+
decryptKeyValue
|
|
21
21
|
} = require('./../helpers/cryptography')
|
|
22
22
|
|
|
23
23
|
const replace = require('./../helpers/replace')
|
|
24
|
-
const dotenvParse = require('./../helpers/dotenvParse')
|
|
25
24
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
26
25
|
|
|
27
26
|
class Decrypt {
|
|
@@ -75,7 +74,7 @@ class Decrypt {
|
|
|
75
74
|
try {
|
|
76
75
|
const encoding = await detectEncoding(filepath)
|
|
77
76
|
let envSrc = await fsx.readFileX(filepath, { encoding })
|
|
78
|
-
const envParsed =
|
|
77
|
+
const envParsed = scan(envSrc).parsed
|
|
79
78
|
|
|
80
79
|
const { privateKeyName } = keyNamesForEnvFile(envFilepath)
|
|
81
80
|
const { privateKeyValue, privateKeySource } = await keyValues(envFilepath, {
|
|
@@ -101,12 +100,12 @@ class Decrypt {
|
|
|
101
100
|
continue
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
103
|
+
const hasEncrypted = values.some(value => encrypted(value))
|
|
104
|
+
if (hasEncrypted) {
|
|
106
105
|
row.keys.push(key) // track key(s)
|
|
107
106
|
|
|
108
107
|
const decryptedValues = values.map(value => {
|
|
109
|
-
if (!
|
|
108
|
+
if (!encrypted(value)) {
|
|
110
109
|
return value
|
|
111
110
|
}
|
|
112
111
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const picomatch = require('picomatch')
|
|
4
|
+
const { encrypted, scan } = require('@dotenvx/primitives')
|
|
4
5
|
|
|
5
6
|
const TYPE_ENV_FILE = 'envFile'
|
|
6
7
|
|
|
@@ -17,14 +18,12 @@ const {
|
|
|
17
18
|
|
|
18
19
|
const {
|
|
19
20
|
encryptValue,
|
|
20
|
-
isEncrypted,
|
|
21
21
|
isPublicKey,
|
|
22
22
|
provision,
|
|
23
23
|
provisionWithPrivateKey
|
|
24
24
|
} = require('./../helpers/cryptography')
|
|
25
25
|
|
|
26
26
|
const replace = require('./../helpers/replace')
|
|
27
|
-
const dotenvParse = require('./../helpers/dotenvParse')
|
|
28
27
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
29
28
|
const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
|
|
30
29
|
|
|
@@ -97,7 +96,7 @@ class Encrypt {
|
|
|
97
96
|
row.kitCreated = 'sample'
|
|
98
97
|
row.changed = true
|
|
99
98
|
}
|
|
100
|
-
const envParsed =
|
|
99
|
+
const envParsed = scan(envSrc).parsed
|
|
101
100
|
|
|
102
101
|
let publicKey
|
|
103
102
|
let privateKey
|
|
@@ -151,12 +150,12 @@ class Encrypt {
|
|
|
151
150
|
continue
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
153
|
+
const fullyEncrypted = values.every(value => encrypted(value) || isPublicKey(key))
|
|
154
|
+
if (!fullyEncrypted) {
|
|
156
155
|
row.keys.push(key) // track key(s)
|
|
157
156
|
|
|
158
157
|
const encryptedValues = values.map(value => {
|
|
159
|
-
if (
|
|
158
|
+
if (encrypted(value) || isPublicKey(key)) {
|
|
160
159
|
return value
|
|
161
160
|
}
|
|
162
161
|
|
|
@@ -4,7 +4,19 @@ const path = require('path')
|
|
|
4
4
|
const Errors = require('../helpers/errors')
|
|
5
5
|
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
6
6
|
const replace = require('../helpers/replace')
|
|
7
|
-
const
|
|
7
|
+
const { scan } = require('@dotenvx/primitives')
|
|
8
|
+
|
|
9
|
+
function finalValues (src) {
|
|
10
|
+
const { parsed } = scan(src)
|
|
11
|
+
const result = {}
|
|
12
|
+
|
|
13
|
+
for (const key in parsed) {
|
|
14
|
+
const values = parsed[key]
|
|
15
|
+
result[key] = values[values.length - 1]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return result
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
class Genexample {
|
|
10
22
|
constructor (directory = '.', envFile) {
|
|
@@ -34,13 +46,13 @@ class Genexample {
|
|
|
34
46
|
const filepath = path.resolve(this.directory, envFilepath)
|
|
35
47
|
if (!fsx.existsSync(filepath)) {
|
|
36
48
|
const error = new Errors({ envFilepath, filepath }).missingEnvFile()
|
|
37
|
-
error.help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx genexample]`
|
|
49
|
+
error.help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx ext genexample]`
|
|
38
50
|
throw error
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
// get the original src
|
|
42
54
|
let src = fsx.readFileXSync(filepath)
|
|
43
|
-
const parsed =
|
|
55
|
+
const parsed = finalValues(src)
|
|
44
56
|
for (const key in parsed) {
|
|
45
57
|
// used later
|
|
46
58
|
keys.add(key)
|
|
@@ -65,7 +77,7 @@ class Genexample {
|
|
|
65
77
|
// it already exists (which means the user might have it modified a way in which they prefer, so replace exampleSrc with their existing .env.example)
|
|
66
78
|
exampleSrc = fsx.readFileXSync(this.exampleFilepath)
|
|
67
79
|
|
|
68
|
-
const parsed =
|
|
80
|
+
const parsed = finalValues(exampleSrc)
|
|
69
81
|
for (const key of [...keys]) {
|
|
70
82
|
if (key in parsed) {
|
|
71
83
|
preExisted[key] = parsed[key]
|
|
@@ -5,8 +5,8 @@ const ignore = require('ignore')
|
|
|
5
5
|
|
|
6
6
|
const Ls = require('../services/ls')
|
|
7
7
|
const Errors = require('../helpers/errors')
|
|
8
|
+
const { sealed } = require('@dotenvx/primitives')
|
|
8
9
|
|
|
9
|
-
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
|
|
10
10
|
const MISSING_DOCKERIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
|
|
11
11
|
|
|
12
12
|
class Prebuild {
|
|
@@ -47,22 +47,22 @@ class Prebuild {
|
|
|
47
47
|
if (file === '.env.example' || file === '.env.x') {
|
|
48
48
|
const warning = new Errors({
|
|
49
49
|
message: `${file} ignored (should not be)`,
|
|
50
|
-
help: `fix: [dotenvx
|
|
50
|
+
help: `fix: [dotenvx gitignore --pattern !${file}]`
|
|
51
51
|
}).custom()
|
|
52
52
|
warnings.push(warning)
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
55
|
if (file !== '.env.example' && file !== '.env.x') {
|
|
56
56
|
const src = fsx.readFileXSync(file)
|
|
57
|
-
const encrypted =
|
|
57
|
+
const encrypted = sealed(src)
|
|
58
58
|
|
|
59
59
|
// if contents are encrypted don't raise an error
|
|
60
60
|
if (!encrypted) {
|
|
61
61
|
let errorMsg = `${file} not encrypted/dockerignored`
|
|
62
|
-
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx
|
|
62
|
+
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx gitignore --pattern ${file}]`
|
|
63
63
|
if (file.includes('.env.keys')) {
|
|
64
64
|
errorMsg = `${file} not dockerignored`
|
|
65
|
-
errorHelp = `fix: [dotenvx
|
|
65
|
+
errorHelp = `fix: [dotenvx gitignore --pattern ${file}]`
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
throw new Errors({ message: errorMsg, help: errorHelp }).custom()
|
|
@@ -4,8 +4,8 @@ const path = require('path')
|
|
|
4
4
|
const ignore = require('ignore')
|
|
5
5
|
|
|
6
6
|
const Ls = require('../services/ls')
|
|
7
|
+
const { sealed } = require('@dotenvx/primitives')
|
|
7
8
|
|
|
8
|
-
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
|
|
9
9
|
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
|
|
10
10
|
const Errors = require('./../helpers/errors')
|
|
11
11
|
const childProcess = require('child_process')
|
|
@@ -63,22 +63,22 @@ class Precommit {
|
|
|
63
63
|
if (file === '.env.example' || file === '.env.x') {
|
|
64
64
|
const warning = new Errors({
|
|
65
65
|
message: `${file} ignored (should not be)`,
|
|
66
|
-
help: `fix: [dotenvx
|
|
66
|
+
help: `fix: [dotenvx gitignore --pattern !${file}]`
|
|
67
67
|
}).custom()
|
|
68
68
|
warnings.push(warning)
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
71
71
|
if (file !== '.env.example' && file !== '.env.x') {
|
|
72
72
|
const src = fsx.readFileXSync(file)
|
|
73
|
-
const encrypted =
|
|
73
|
+
const encrypted = sealed(src)
|
|
74
74
|
|
|
75
75
|
// if contents are encrypted don't raise an error
|
|
76
76
|
if (!encrypted) {
|
|
77
77
|
let errorMsg = `${file} not encrypted/gitignored`
|
|
78
|
-
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx
|
|
78
|
+
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx gitignore --pattern ${file}]`
|
|
79
79
|
if (file.includes('.env.keys')) {
|
|
80
80
|
errorMsg = `${file} not gitignored`
|
|
81
|
-
errorHelp = `fix: [dotenvx
|
|
81
|
+
errorHelp = `fix: [dotenvx gitignore --pattern ${file}]`
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
throw new Errors({ message: errorMsg, help: errorHelp }).custom()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const picomatch = require('picomatch')
|
|
4
|
+
const { encrypted, scan } = require('@dotenvx/primitives')
|
|
4
5
|
|
|
5
6
|
const TYPE_ENV_FILE = 'envFile'
|
|
6
7
|
|
|
@@ -19,13 +20,11 @@ const {
|
|
|
19
20
|
armorKeypair,
|
|
20
21
|
localKeypair,
|
|
21
22
|
encryptValue,
|
|
22
|
-
decryptKeyValue
|
|
23
|
-
isEncrypted
|
|
23
|
+
decryptKeyValue
|
|
24
24
|
} = require('./../helpers/cryptography')
|
|
25
25
|
|
|
26
26
|
const append = require('./../helpers/append')
|
|
27
27
|
const replace = require('./../helpers/replace')
|
|
28
|
-
const dotenvParse = require('./../helpers/dotenvParse')
|
|
29
28
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
30
29
|
|
|
31
30
|
class Rotate {
|
|
@@ -81,7 +80,7 @@ class Rotate {
|
|
|
81
80
|
try {
|
|
82
81
|
const encoding = await detectEncoding(filepath)
|
|
83
82
|
let envSrc = await fsx.readFileX(filepath, { encoding })
|
|
84
|
-
const envParsed =
|
|
83
|
+
const envParsed = scan(envSrc).parsed
|
|
85
84
|
|
|
86
85
|
const { publicKeyName, privateKeyName } = keyNamesForEnvFile(envFilepath)
|
|
87
86
|
const { privateKeyValue } = await keyValues(envFilepath, {
|
|
@@ -134,7 +133,7 @@ class Rotate {
|
|
|
134
133
|
continue
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
const value = [...values].reverse().find(value =>
|
|
136
|
+
const value = [...values].reverse().find(value => encrypted(value))
|
|
138
137
|
if (value) { // only re-encrypt those already encrypted
|
|
139
138
|
row.keys.push(key) // track key(s)
|
|
140
139
|
|
package/src/lib/services/sets.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fsx = require('./../helpers/fsx')
|
|
2
2
|
const path = require('path')
|
|
3
|
+
const { encrypted, scan } = require('@dotenvx/primitives')
|
|
3
4
|
|
|
4
5
|
const TYPE_ENV_FILE = 'envFile'
|
|
5
6
|
|
|
@@ -18,19 +19,22 @@ const {
|
|
|
18
19
|
const {
|
|
19
20
|
encryptValue,
|
|
20
21
|
decryptKeyValue,
|
|
21
|
-
isEncrypted,
|
|
22
22
|
provision,
|
|
23
23
|
provisionSync,
|
|
24
24
|
provisionWithPrivateKey
|
|
25
25
|
} = require('./../helpers/cryptography')
|
|
26
26
|
|
|
27
27
|
const replace = require('./../helpers/replace')
|
|
28
|
-
const dotenvParse = require('./../helpers/dotenvParse')
|
|
29
28
|
const detectEncoding = require('./../helpers/detectEncoding')
|
|
30
29
|
const detectEncodingSync = require('./../helpers/detectEncodingSync')
|
|
31
30
|
|
|
32
31
|
function allValuesForKey (envSrc, key) {
|
|
33
|
-
return
|
|
32
|
+
return scan(envSrc).parsed[key] || []
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function finalValueForKey (envSrc, key) {
|
|
36
|
+
const values = allValuesForKey(envSrc, key)
|
|
37
|
+
return values.length > 0 ? values[values.length - 1] : null
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
class Sets {
|
|
@@ -119,12 +123,11 @@ class Sets {
|
|
|
119
123
|
seededWithInitialKey = true
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
row.originalValue = envParsed[row.key] || null
|
|
126
|
+
row.originalValue = finalValueForKey(envSrc, row.key)
|
|
124
127
|
if (seededWithInitialKey) {
|
|
125
128
|
row.originalValue = null
|
|
126
129
|
}
|
|
127
|
-
const wasPlainText = !
|
|
130
|
+
const wasPlainText = !encrypted(row.originalValue)
|
|
128
131
|
this.readableFilepaths.add(envFilepath)
|
|
129
132
|
|
|
130
133
|
if (this.encrypt) {
|
|
@@ -236,12 +239,11 @@ class Sets {
|
|
|
236
239
|
seededWithInitialKey = true
|
|
237
240
|
}
|
|
238
241
|
|
|
239
|
-
|
|
240
|
-
row.originalValue = envParsed[row.key] || null
|
|
242
|
+
row.originalValue = finalValueForKey(envSrc, row.key)
|
|
241
243
|
if (seededWithInitialKey) {
|
|
242
244
|
row.originalValue = null
|
|
243
245
|
}
|
|
244
|
-
const wasPlainText = !
|
|
246
|
+
const wasPlainText = !encrypted(row.originalValue)
|
|
245
247
|
this.readableFilepaths.add(envFilepath)
|
|
246
248
|
|
|
247
249
|
if (this.encrypt) {
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// historical dotenv.parse - https://github.com/motdotla/dotenv)
|
|
2
|
-
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
|
|
3
|
-
|
|
4
|
-
function dotenvParse (src, skipExpandForDoubleQuotes = false, skipConvertingWindowsNewlines = false, collectAllValues = false) {
|
|
5
|
-
const obj = {}
|
|
6
|
-
|
|
7
|
-
// Convert buffer to string
|
|
8
|
-
let lines = src.toString()
|
|
9
|
-
|
|
10
|
-
// Convert line breaks to same format
|
|
11
|
-
if (!skipConvertingWindowsNewlines) {
|
|
12
|
-
lines = lines.replace(/\r\n?/mg, '\n')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
let match
|
|
16
|
-
while ((match = LINE.exec(lines)) != null) {
|
|
17
|
-
const key = match[1]
|
|
18
|
-
|
|
19
|
-
// Default undefined or null to empty string
|
|
20
|
-
let value = (match[2] || '')
|
|
21
|
-
|
|
22
|
-
// Remove whitespace
|
|
23
|
-
value = value.trim()
|
|
24
|
-
|
|
25
|
-
// Check if double quoted
|
|
26
|
-
const maybeQuote = value[0]
|
|
27
|
-
|
|
28
|
-
// Remove surrounding quotes
|
|
29
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
|
|
30
|
-
|
|
31
|
-
// Expand newlines if double quoted
|
|
32
|
-
if (maybeQuote === '"' && !skipExpandForDoubleQuotes) {
|
|
33
|
-
value = value.replace(/\\n/g, '\n') // newline
|
|
34
|
-
value = value.replace(/\\r/g, '\r') // carriage return
|
|
35
|
-
value = value.replace(/\\t/g, '\t') // tabs
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (collectAllValues) {
|
|
39
|
-
// handle scenario where user mistakenly includes plaintext duplicate in .env:
|
|
40
|
-
//
|
|
41
|
-
// # .env
|
|
42
|
-
// HELLO="World"
|
|
43
|
-
// HELLO="encrypted:1234"
|
|
44
|
-
obj[key] = obj[key] || []
|
|
45
|
-
obj[key].push(value)
|
|
46
|
-
} else {
|
|
47
|
-
// Add to object
|
|
48
|
-
obj[key] = value
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return obj
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = dotenvParse
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const dotenvParse = require('./dotenvParse')
|
|
2
|
-
const isEncrypted = require('./cryptography/isEncrypted')
|
|
3
|
-
const isPublicKey = require('./cryptography/isPublicKey')
|
|
4
|
-
|
|
5
|
-
function isFullyEncrypted (src) {
|
|
6
|
-
const parsed = dotenvParse(src, false, false, true) // collect all values
|
|
7
|
-
|
|
8
|
-
for (const [key, values] of Object.entries(parsed)) {
|
|
9
|
-
// handle scenario where user mistakenly includes plaintext duplicate in .env:
|
|
10
|
-
//
|
|
11
|
-
// # .env
|
|
12
|
-
// HELLO="World"
|
|
13
|
-
// HELLO="encrypted:1234"
|
|
14
|
-
//
|
|
15
|
-
// key => [value1, ...]
|
|
16
|
-
for (const value of values) {
|
|
17
|
-
const result = isEncrypted(value) || isPublicKey(key)
|
|
18
|
-
if (!result) {
|
|
19
|
-
return false
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return true
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
module.exports = isFullyEncrypted
|