@dotenvx/dotenvx 1.75.1 → 2.0.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 (85) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README.md +4 -114
  3. package/package.json +8 -7
  4. package/src/cli/actions/decrypt.js +35 -17
  5. package/src/cli/actions/doctor.js +2 -2
  6. package/src/cli/actions/encrypt.js +64 -85
  7. package/src/cli/actions/ext/genexample.js +2 -2
  8. package/src/cli/actions/get.js +16 -7
  9. package/src/cli/actions/keypair.js +55 -42
  10. package/src/cli/actions/login.js +4 -3
  11. package/src/cli/actions/normalizeArmorAliases.js +9 -0
  12. package/src/cli/actions/run.js +65 -22
  13. package/src/cli/actions/set.js +51 -37
  14. package/src/cli/dotenvx.js +6 -21
  15. package/src/lib/api/{postKeypair.js → postArmorKeyring.js} +4 -14
  16. package/src/lib/{helpers/envResolution → conventions}/environment.js +1 -1
  17. package/src/lib/conventions/filepaths.js +9 -0
  18. package/src/lib/conventions/keynames.js +38 -0
  19. package/src/lib/helpers/catchAndLog.js +4 -1
  20. package/src/lib/helpers/cryptography/index.js +0 -8
  21. package/src/lib/helpers/cryptography/mutateKeysSrc.js +5 -21
  22. package/src/lib/helpers/envResolution/index.js +1 -1
  23. package/src/lib/helpers/errors.js +13 -0
  24. package/src/lib/helpers/keypairMetadata.js +1 -1
  25. package/src/lib/main.d.ts +2 -39
  26. package/src/lib/main.js +82 -45
  27. package/src/lib/providers/armor/index.js +29 -0
  28. package/src/lib/providers/index.js +46 -0
  29. package/src/lib/providers/worker.js +6 -0
  30. package/src/lib/resolvers/envs.js +302 -0
  31. package/src/lib/resolvers/get.js +69 -0
  32. package/src/lib/resolvers/keypair.js +81 -0
  33. package/src/lib/resolvers/ls.js +41 -0
  34. package/src/lib/services/armorDown.js +2 -2
  35. package/src/lib/services/{armorKeypair.js → armorKeyring.js} +14 -51
  36. package/src/lib/services/armorMove.js +2 -2
  37. package/src/lib/services/armorPull.js +2 -2
  38. package/src/lib/services/armorPush.js +2 -2
  39. package/src/lib/services/armorUp.js +2 -2
  40. package/src/lib/services/genexample.js +5 -6
  41. package/src/lib/services/login.js +25 -0
  42. package/src/lib/services/prebuild.js +9 -3
  43. package/src/lib/services/precommit.js +9 -3
  44. package/src/lib/transforms/decrypt.js +83 -0
  45. package/src/lib/transforms/encrypt.js +179 -0
  46. package/src/lib/transforms/set.js +179 -0
  47. package/src/shared/logger.js +5 -0
  48. package/src/cli/actions/normalizeArmorOptions.js +0 -10
  49. package/src/cli/actions/rotate.js +0 -87
  50. package/src/lib/helpers/append.js +0 -62
  51. package/src/lib/helpers/cryptography/armorKeypair.js +0 -42
  52. package/src/lib/helpers/cryptography/armorKeypairSync.js +0 -55
  53. package/src/lib/helpers/cryptography/encryptValue.js +0 -7
  54. package/src/lib/helpers/cryptography/localKeypair.js +0 -14
  55. package/src/lib/helpers/cryptography/mutateKeysSrcSync.js +0 -38
  56. package/src/lib/helpers/cryptography/provision.js +0 -56
  57. package/src/lib/helpers/cryptography/provisionSync.js +0 -51
  58. package/src/lib/helpers/cryptography/provisionWithPrivateKey.js +0 -26
  59. package/src/lib/helpers/escapeDollarSigns.js +0 -5
  60. package/src/lib/helpers/escapeForRegex.js +0 -5
  61. package/src/lib/helpers/evalKeyValue.js +0 -23
  62. package/src/lib/helpers/isIgnoringDotenvKeys.js +0 -19
  63. package/src/lib/helpers/keyResolution/index.js +0 -16
  64. package/src/lib/helpers/keyResolution/keyNamesForEnvFile.js +0 -24
  65. package/src/lib/helpers/keyResolution/keyValues.js +0 -99
  66. package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +0 -85
  67. package/src/lib/helpers/keyResolution/keyValuesSync.js +0 -103
  68. package/src/lib/helpers/keyResolution/readFileKey.js +0 -19
  69. package/src/lib/helpers/keyResolution/readFileKeySync.js +0 -17
  70. package/src/lib/helpers/keyResolution/readProcessKey.js +0 -7
  71. package/src/lib/helpers/localDisplayPath.js +0 -11
  72. package/src/lib/helpers/parse.js +0 -214
  73. package/src/lib/helpers/pluralize.js +0 -10
  74. package/src/lib/helpers/quotes.js +0 -36
  75. package/src/lib/helpers/replace.js +0 -82
  76. package/src/lib/helpers/resolveEscapeSequences.js +0 -5
  77. package/src/lib/services/decrypt.js +0 -156
  78. package/src/lib/services/encrypt.js +0 -213
  79. package/src/lib/services/get.js +0 -76
  80. package/src/lib/services/keypair.js +0 -60
  81. package/src/lib/services/loginPoll.js +0 -36
  82. package/src/lib/services/ls.js +0 -57
  83. package/src/lib/services/rotate.js +0 -197
  84. package/src/lib/services/run.js +0 -255
  85. package/src/lib/services/sets.js +0 -328
@@ -6,32 +6,16 @@ const FIRST_TIME_KEYS_SRC = [
6
6
  '#/----------------------------------------------------------/'
7
7
  ].join('\n')
8
8
 
9
- const path = require('path')
10
- const fsx = require('./../fsx')
9
+ function mutateKeysSrc ({ keysSrc, privateKeyName, privateKeyValue, comment }) {
10
+ const appendPrivateKey = [`# ${comment}`, `${privateKeyName}=${privateKeyValue}`, ''].join('\n')
11
11
 
12
- async function mutateKeysSrc ({ envFilepath, keysFilepath, privateKeyName, privateKeyValue }) {
13
- const filename = path.basename(envFilepath)
14
- const filepath = path.resolve(envFilepath)
15
- let resolvedKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
16
- if (keysFilepath) {
17
- resolvedKeysFilepath = path.resolve(keysFilepath)
12
+ if (!keysSrc || keysSrc.length < 1) {
13
+ keysSrc = `${FIRST_TIME_KEYS_SRC}\n`
18
14
  }
19
- const appendPrivateKey = [`# ${filename}`, `${privateKeyName}=${privateKeyValue}`, ''].join('\n')
20
-
21
- let keysSrc = ''
22
- if (await fsx.exists(resolvedKeysFilepath)) {
23
- keysSrc = await fsx.readFileX(resolvedKeysFilepath)
24
- }
25
- keysSrc = keysSrc.length > 1 ? keysSrc : `${FIRST_TIME_KEYS_SRC}\n`
26
15
  keysSrc = `${keysSrc}\n${appendPrivateKey}`
27
16
 
28
- await fsx.writeFileX(resolvedKeysFilepath, keysSrc) // TODO: don't write if armor
29
-
30
- const envKeysFilepath = keysFilepath || path.join(path.dirname(envFilepath), path.basename(resolvedKeysFilepath))
31
-
32
17
  return {
33
- keysSrc,
34
- envKeysFilepath
18
+ keysSrc
35
19
  }
36
20
  }
37
21
 
@@ -3,6 +3,6 @@ module.exports = {
3
3
  determine: require('./determine'),
4
4
  findEnvFiles: require('./../findEnvFiles'),
5
5
  dotenvOptionPaths: require('./../dotenvOptionPaths'),
6
- environment: require('./environment'),
6
+ environment: require('./../../conventions/environment'),
7
7
  conventions: require('./../conventions')
8
8
  }
@@ -18,6 +18,7 @@ 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/new',
21
22
  PRECOMMIT_HOOK_MODIFY_FAILED: 'try again or report error',
22
23
  WRONG_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/466'
23
24
  }
@@ -265,6 +266,18 @@ class Errors {
265
266
  return e
266
267
  }
267
268
 
269
+ missingPublicKey () {
270
+ const code = 'MISSING_PUBLIC_KEY'
271
+ const message = `[${code}] missing public key`
272
+ const help = `fix: [${ISSUE_BY_CODE[code]}]`
273
+
274
+ const e = new Error(message)
275
+ e.code = code
276
+ e.help = help
277
+ e.messageWithHelp = `${message}. ${help}`
278
+ return e
279
+ }
280
+
268
281
  precommitHookModifyFailed () {
269
282
  const code = 'PRECOMMIT_HOOK_MODIFY_FAILED'
270
283
  const message = `[${code}] failed to modify pre-commit hook: ${this.error.message}`
@@ -3,7 +3,7 @@ const fs = require('fs')
3
3
  const execa = require('execa')
4
4
  const { scan } = require('@dotenvx/primitives')
5
5
  const canonicalEnvFilename = require('./canonicalEnvFilename')
6
- const environment = require('./envResolution/environment')
6
+ const environment = require('../conventions/environment')
7
7
  const sanitizeCommandForMetadata = require('./sanitizeCommandForMetadata')
8
8
 
9
9
  function compact (object) {
package/src/lib/main.d.ts CHANGED
@@ -306,7 +306,7 @@ export function set(
306
306
  key: string,
307
307
  value: string,
308
308
  options?: SetOptions
309
- ): SetOutput;
309
+ ): Promise<SetOutput>;
310
310
 
311
311
  export interface GetOptions {
312
312
  /**
@@ -367,7 +367,7 @@ export interface GetOptions {
367
367
  export function get(
368
368
  key: string,
369
369
  options?: GetOptions
370
- ): string;
370
+ ): Promise<string>;
371
371
 
372
372
  /**
373
373
  * List all env files in the current working directory
@@ -381,40 +381,3 @@ export function ls(
381
381
  envFile: string | string[],
382
382
  excludeEnvFile: string | string[]
383
383
  ): string[];
384
-
385
- export type DoctorFinding = {
386
- lang: string;
387
- filepath: string;
388
- line: number;
389
- code: string;
390
- msg: string;
391
- };
392
-
393
- /**
394
- * Scan code for dotenv loaders that can conflict with dotenvx.
395
- *
396
- * @param directory - directory to scan
397
- */
398
- export function doctor(
399
- directory: string
400
- ): DoctorFinding[];
401
-
402
- export type GenExampleOutput = {
403
- envExampleFile: string;
404
- envFile: string | string[];
405
- exampleFilepath: string;
406
- addedKeys: string[];
407
- injected: Record<string, string>;
408
- preExisted: Record<string, string>;
409
- };
410
-
411
- /**
412
- * Generate an example .env file
413
- *
414
- * @param directory - current working directory
415
- * @param envFile - path to the .env file(s)
416
- */
417
- export function genexample(
418
- directory: string,
419
- envFile: string
420
- ): GenExampleOutput;
package/src/lib/main.js CHANGED
@@ -1,25 +1,37 @@
1
1
  // @ts-check
2
2
  const path = require('path')
3
+ const { encrypted, parseSync } = require('@dotenvx/primitives')
3
4
 
4
5
  // shared
5
6
  const { setLogLevel, setLogName, setLogVersion, logger } = require('./../shared/logger')
6
7
  const { getColor, bold } = require('./../shared/colors')
7
8
 
8
- // services
9
- const Ls = require('./services/ls')
10
- const Doctor = require('./services/doctor')
11
- const Run = require('./services/run')
12
- const Sets = require('./services/sets')
13
- const Get = require('./services/get')
14
- const Keypair = require('./services/keypair')
15
- const Genexample = require('./services/genexample')
9
+ // resolvers
10
+ const envsResolver = require('./resolvers/envs')
11
+ const getResolver = require('./resolvers/get')
12
+ const lsResolver = require('./resolvers/ls')
13
+
16
14
  const Session = require('./../db/session')
17
15
 
16
+ // transforms
17
+ const setTransform = require('./transforms/set')
18
+
18
19
  // helpers
19
20
  const buildEnvs = require('./helpers/buildEnvs')
20
21
  const { determine } = require('./helpers/envResolution')
21
- const Parse = require('./helpers/parse')
22
22
  const fsx = require('./helpers/fsx')
23
+ const decryptKeyValue = require('./helpers/cryptography/decryptKeyValue')
24
+ const Errors = require('./helpers/errors')
25
+
26
+ function uniqueInjectedKeys (processedEnvs) {
27
+ const result = new Set()
28
+ for (const processedEnv of processedEnvs) {
29
+ for (const key of Object.keys(processedEnv.injected || {})) {
30
+ result.add(key)
31
+ }
32
+ }
33
+ return result
34
+ }
23
35
 
24
36
  /** @type {import('./main').config} */
25
37
  const config = function (options = {}) {
@@ -57,12 +69,16 @@ const config = function (options = {}) {
57
69
  }
58
70
  const {
59
71
  processedEnvs,
60
- readableFilepaths,
61
- uniqueInjectedKeys
62
- } = new Run(envs, overload, processEnv, envKeysFile, noArmor, {
72
+ readableFilepaths
73
+ } = envsResolver.sync({
74
+ envs,
75
+ overload,
76
+ processEnv,
77
+ envKeysFile,
78
+ noArmor,
63
79
  noSpinner: options.noSpinner,
64
80
  token: options.token
65
- }).runSync()
81
+ })
66
82
 
67
83
  let lastError
68
84
  /** @type {Record<string, string>} */
@@ -92,7 +108,7 @@ const config = function (options = {}) {
92
108
  }
93
109
 
94
110
  Object.assign(parsedAll, processedEnv.injected || {})
95
- Object.assign(parsedAll, processedEnv.preExisted || {}) // preExisted 'wins'
111
+ Object.assign(parsedAll, processedEnv.existed || {}) // existed 'wins'
96
112
 
97
113
  // debug parsed
98
114
  logger.debug(processedEnv.parsed)
@@ -103,14 +119,14 @@ const config = function (options = {}) {
103
119
  logger.debug(`${key} set to ${value}`)
104
120
  }
105
121
 
106
- // verbose/debug preExisted key/value
107
- for (const [key, value] of Object.entries(processedEnv.preExisted || {})) {
122
+ // verbose/debug existed key/value
123
+ for (const [key, value] of Object.entries(processedEnv.existed || {})) {
108
124
  logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
109
125
  logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
110
126
  }
111
127
  }
112
128
 
113
- let msg = `injected env (${uniqueInjectedKeys.length})`
129
+ let msg = `injected env (${uniqueInjectedKeys(processedEnvs).size})`
114
130
  if (readableFilepaths.length > 0) {
115
131
  msg += ` from ${readableFilepaths.join(', ')}`
116
132
  }
@@ -147,7 +163,29 @@ const parse = function (src, options = {}) {
147
163
  // ignore
148
164
  const ignore = options.ignore || []
149
165
 
150
- const { parsed, errors } = new Parse(src, privateKey, processEnv, overload).run()
166
+ if (privateKey) {
167
+ processEnv = Object.assign({}, processEnv, { DOTENV_PRIVATE_KEY: privateKey })
168
+ }
169
+
170
+ const { parsed } = parseSync(src, { processEnv, overload })
171
+ const errors = []
172
+
173
+ for (const key of Object.keys(parsed)) {
174
+ if (!encrypted(parsed[key])) {
175
+ continue
176
+ }
177
+
178
+ if (!privateKey) {
179
+ errors.push(new Errors({ key, privateKeyName: 'DOTENV_PRIVATE_KEY', privateKey }).missingPrivateKey())
180
+ continue
181
+ }
182
+
183
+ try {
184
+ parsed[key] = decryptKeyValue(key, parsed[key], 'DOTENV_PRIVATE_KEY', privateKey)
185
+ } catch (error) {
186
+ errors.push(error)
187
+ }
188
+ }
151
189
 
152
190
  // display any errors
153
191
  for (const error of errors) {
@@ -163,7 +201,7 @@ const parse = function (src, options = {}) {
163
201
  }
164
202
 
165
203
  /* @type {import('./main').set} */
166
- const set = function (key, value, options = {}) {
204
+ const set = async function (key, value, options = {}) {
167
205
  // encrypt
168
206
  let encrypt = true
169
207
  if (options.plain) {
@@ -180,13 +218,27 @@ const set = function (key, value, options = {}) {
180
218
 
181
219
  const envs = buildEnvs(options)
182
220
  const envKeysFilepath = options.envKeysFile
221
+ const noCreate = options.create === false
183
222
  const noArmor = resolveNoArmor(options)
184
223
 
185
224
  const {
225
+ keysSrc,
186
226
  processedEnvs,
187
227
  changedFilepaths,
188
228
  unchangedFilepaths
189
- } = new Sets(key, value, envs, encrypt, envKeysFilepath, noArmor).runSync()
229
+ } = await setTransform({
230
+ envs,
231
+ key,
232
+ value,
233
+ fk: envKeysFilepath,
234
+ noArmor,
235
+ noCreate,
236
+ encrypt
237
+ })
238
+
239
+ if (keysSrc) {
240
+ fsx.writeFileXSync(envKeysFilepath || '.env.keys', keysSrc)
241
+ }
190
242
 
191
243
  let withEncryption = ''
192
244
 
@@ -242,14 +294,21 @@ const set = function (key, value, options = {}) {
242
294
  }
243
295
 
244
296
  /* @type {import('./main').get} */
245
- const get = function (key, options = {}) {
297
+ const get = async function (key, options = {}) {
246
298
  const envs = buildEnvs(options)
247
299
  const noArmor = resolveNoArmor(options)
248
300
 
249
301
  // ignore
250
302
  const ignore = options.ignore || []
251
303
 
252
- const { parsed, errors } = new Get(key, envs, options.overload, options.all, options.envKeysFile, noArmor).runSync()
304
+ const { parsed, errors } = await getResolver({
305
+ key,
306
+ envs,
307
+ overload: options.overload,
308
+ all: options.all,
309
+ envKeysFile: options.envKeysFile,
310
+ noArmor
311
+ })
253
312
 
254
313
  for (const error of errors || []) {
255
314
  if (ignore.includes(error.code)) {
@@ -301,26 +360,7 @@ const get = function (key, options = {}) {
301
360
 
302
361
  /** @type {import('./main').ls} */
303
362
  const ls = function (directory, envFile, excludeEnvFile) {
304
- return new Ls(directory, envFile, excludeEnvFile).run()
305
- }
306
-
307
- const doctor = function (directory) {
308
- return new Doctor(directory).run()
309
- }
310
-
311
- /** @type {import('./main').genexample} */
312
- const genexample = function (directory, envFile) {
313
- return new Genexample(directory, envFile).run()
314
- }
315
-
316
- /** @type {import('./main').keypair} */
317
- const keypair = function (envFile, key, envKeysFile = null, noArmor = false) {
318
- const keypairs = new Keypair(envFile, envKeysFile, noArmor).runSync()
319
- if (key) {
320
- return keypairs[key]
321
- } else {
322
- return keypairs
323
- }
363
+ return lsResolver({ directory, envFile, excludeEnvFile })
324
364
  }
325
365
 
326
366
  function resolveNoArmor (options = {}) {
@@ -336,9 +376,6 @@ module.exports = {
336
376
  set,
337
377
  get,
338
378
  ls,
339
- doctor,
340
- keypair,
341
- genexample,
342
379
  // expose for libs depending on @dotenvx/dotenvx - like dotenvx-ops
343
380
  setLogLevel,
344
381
  logger,
@@ -0,0 +1,29 @@
1
+ const Session = require('../../../db/session')
2
+ const ArmorKeyring = require('../../services/armorKeyring')
3
+ const armoredKeyDisplay = require('../../helpers/armoredKeyDisplay')
4
+
5
+ async function index (publicKeyHex, options = {}) {
6
+ const sesh = new Session()
7
+
8
+ const hostname = sesh.hostname()
9
+ const token = sesh.token()
10
+ const devicePublicKey = sesh.devicePublicKey()
11
+
12
+ const keyring = new ArmorKeyring(
13
+ hostname,
14
+ token,
15
+ devicePublicKey,
16
+ publicKeyHex
17
+ )
18
+ if (options.onStatus) {
19
+ keyring.onApprovalRequired = ({ approvalUri, code }) => {
20
+ const keyDisplay = armoredKeyDisplay(publicKeyHex)
21
+ const keySuffix = keyDisplay ? ` (${keyDisplay})` : ''
22
+ options.onStatus(`[${code}] visit [${approvalUri}] and approve${keySuffix}`)
23
+ }
24
+ }
25
+
26
+ return await keyring.run() // { "publicKey": "privateKey" }
27
+ }
28
+
29
+ module.exports = index
@@ -0,0 +1,46 @@
1
+ const Session = require('./../../db/session')
2
+
3
+ const armorProvider = require('./armor/index')
4
+ function syncArmorProvider (publicKeyHex) {
5
+ const { createSyncFn } = require('synckit')
6
+ const runProviderSync = createSyncFn(require.resolve('./worker.js'))
7
+ return runProviderSync(require.resolve('./armor/index'), publicKeyHex)
8
+ }
9
+
10
+ async function providers (options = {}) {
11
+ if (Object.prototype.hasOwnProperty.call(options, 'provider')) {
12
+ return options.provider
13
+ }
14
+
15
+ if (options.noArmor || options.armor === false) {
16
+ return null
17
+ }
18
+
19
+ const sesh = new Session()
20
+ const noArmor = !options.token && await sesh.noArmor()
21
+ if (noArmor) return null
22
+
23
+ return (publicKeyHex) => armorProvider(publicKeyHex, {
24
+ onStatus: options.onStatus,
25
+ token: options.token,
26
+ command: options.command
27
+ })
28
+ }
29
+
30
+ providers.sync = function providersSync (options = {}) {
31
+ if (Object.prototype.hasOwnProperty.call(options, 'provider')) {
32
+ return options.provider
33
+ }
34
+
35
+ if (options.noArmor || options.armor === false) {
36
+ return null
37
+ }
38
+
39
+ const sesh = new Session()
40
+ const noArmor = !options.token && sesh.noArmorSync()
41
+ if (noArmor) return null
42
+
43
+ return syncArmorProvider
44
+ }
45
+
46
+ module.exports = providers
@@ -0,0 +1,6 @@
1
+ const { runAsWorker } = require('synckit')
2
+
3
+ runAsWorker(async (providerPath, publicKeyHex) => {
4
+ const provider = require(providerPath)
5
+ return provider(publicKeyHex)
6
+ })