@dotenvx/dotenvx
Advanced tools
Comparing version 1.19.3 to 1.20.0
@@ -5,4 +5,15 @@ # Changelog | ||
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.19.3...main) | ||
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.20.0...main) | ||
### Changed | ||
* respect `process.env.DOTENV_PRIVATE_KEY` and/or `process.env.DOTENV_PUBLIC_KEY` on `set` ([#401](https://github.com/dotenvx/dotenvx/pull/401)) | ||
* respect `process.env.DOTENV_PRIVATE_KEY` and/or `process.env.DOTENV_PUBLIC_KEY` on `encrypt` ([#411](https://github.com/dotenvx/dotenvx/pull/411)) | ||
* respect `process.env.DOTENV_PRIVATE_KEY` on `decrypt` ([#412](https://github.com/dotenvx/dotenvx/pull/412)) | ||
* change `logger.help` to use brighter blue ([#414](https://github.com/dotenvx/dotenvx/pull/414)) | ||
### Removed | ||
* remove `main.decrypt,encrypt,set` ([#410](https://github.com/dotenvx/dotenvx/pull/410)) | ||
## 1.19.3 | ||
@@ -9,0 +20,0 @@ |
{ | ||
"version": "1.19.3", | ||
"version": "1.20.0", | ||
"name": "@dotenvx/dotenvx", | ||
@@ -4,0 +4,0 @@ "description": "a better dotenv–from the creator of `dotenv`", |
@@ -1793,3 +1793,3 @@ [![dotenvx](https://dotenvx.com/better-banner.png)](https://dotenvx.com) | ||
Run `$ dotenvx vault migrate` and follow the instructions. | ||
Run `$ dotenvx ext vault migrate` and follow the instructions. | ||
@@ -1796,0 +1796,0 @@ |
const fsx = require('./../../lib/helpers/fsx') | ||
const { logger } = require('./../../shared/logger') | ||
const main = require('./../../lib/main') | ||
const Decrypt = require('./../../lib/services/decrypt') | ||
const catchAndLog = require('../../lib/helpers/catchAndLog') | ||
function decrypt () { | ||
@@ -10,2 +12,4 @@ const options = this.opts() | ||
const envs = this.envs | ||
let errorCount = 0 | ||
@@ -16,11 +20,11 @@ | ||
const { | ||
processedEnvFiles | ||
} = main.decrypt(options.envFile, options.key, options.excludeKey) | ||
processedEnvs | ||
} = new Decrypt(envs, options.key, options.excludeKey).run() | ||
for (const processedEnvFile of processedEnvFiles) { | ||
if (processedEnvFile.error) { | ||
for (const processedEnv of processedEnvs) { | ||
if (processedEnv.error) { | ||
errorCount += 1 | ||
console.error(processedEnvFile.error.message) | ||
console.error(processedEnv.error.message) | ||
} else { | ||
console.log(processedEnvFile.envSrc) | ||
console.log(processedEnv.envSrc) | ||
} | ||
@@ -37,25 +41,25 @@ } | ||
const { | ||
processedEnvFiles, | ||
processedEnvs, | ||
changedFilepaths, | ||
unchangedFilepaths | ||
} = main.decrypt(options.envFile, options.key, options.excludeKey) | ||
} = new Decrypt(envs, options.key, options.excludeKey).run() | ||
for (const processedEnvFile of processedEnvFiles) { | ||
logger.verbose(`decrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
for (const processedEnv of processedEnvs) { | ||
logger.verbose(`decrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
if (processedEnvFile.error) { | ||
if (processedEnv.error) { | ||
errorCount += 1 | ||
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') { | ||
logger.error(processedEnvFile.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx decrypt]`) | ||
if (processedEnv.error.code === 'MISSING_ENV_FILE') { | ||
logger.error(processedEnv.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx decrypt]`) | ||
} else { | ||
logger.error(processedEnvFile.error.message) | ||
logger.error(processedEnv.error.message) | ||
} | ||
} else if (processedEnvFile.changed) { | ||
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc) | ||
} else if (processedEnv.changed) { | ||
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc) | ||
logger.verbose(`decrypted ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
logger.verbose(`decrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
} else { | ||
logger.verbose(`no changes ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
} | ||
@@ -76,12 +80,3 @@ } | ||
} catch (error) { | ||
logger.error(error.message) | ||
if (error.help) { | ||
logger.help(error.help) | ||
} | ||
if (error.debug) { | ||
logger.debug(error.debug) | ||
} | ||
if (error.code) { | ||
logger.debug(`ERROR_CODE: ${error.code}`) | ||
} | ||
catchAndLog(error) | ||
process.exit(1) | ||
@@ -88,0 +83,0 @@ } |
const fsx = require('./../../lib/helpers/fsx') | ||
const { logger } = require('./../../shared/logger') | ||
const main = require('./../../lib/main') | ||
const Encrypt = require('./../../lib/services/encrypt') | ||
const catchAndLog = require('../../lib/helpers/catchAndLog') | ||
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys') | ||
@@ -12,10 +13,12 @@ | ||
const envs = this.envs | ||
// stdout - should not have a try so that exit codes can surface to stdout | ||
if (options.stdout) { | ||
const { | ||
processedEnvFiles | ||
} = main.encrypt(options.envFile, options.key, options.excludeKey) | ||
processedEnvs | ||
} = new Encrypt(envs, options.key, options.excludeKey).run() | ||
for (const processedEnvFile of processedEnvFiles) { | ||
console.log(processedEnvFile.envSrc) | ||
for (const processedEnv of processedEnvs) { | ||
console.log(processedEnv.envSrc) | ||
} | ||
@@ -26,22 +29,25 @@ process.exit(0) // exit early | ||
const { | ||
processedEnvFiles, | ||
processedEnvs, | ||
changedFilepaths, | ||
unchangedFilepaths | ||
} = main.encrypt(options.envFile, options.key, options.excludeKey) | ||
} = new Encrypt(envs, options.key, options.excludeKey).run() | ||
for (const processedEnvFile of processedEnvFiles) { | ||
logger.verbose(`encrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
if (processedEnvFile.error) { | ||
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') { | ||
logger.warn(processedEnvFile.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx encrypt]`) | ||
for (const processedEnv of processedEnvs) { | ||
logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
if (processedEnv.error) { | ||
if (processedEnv.error.code === 'MISSING_ENV_FILE') { | ||
logger.warn(processedEnv.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx encrypt]`) | ||
} else { | ||
logger.warn(processedEnvFile.error.message) | ||
logger.warn(processedEnv.error.message) | ||
if (processedEnv.error.help) { | ||
logger.help(processedEnv.error.help) | ||
} | ||
} | ||
} else if (processedEnvFile.changed) { | ||
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc) | ||
} else if (processedEnv.changed) { | ||
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc) | ||
logger.verbose(`encrypted ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
logger.verbose(`encrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
} else { | ||
logger.verbose(`no changes ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`) | ||
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`) | ||
} | ||
@@ -58,5 +64,5 @@ } | ||
for (const processedEnvFile of processedEnvFiles) { | ||
if (processedEnvFile.privateKeyAdded) { | ||
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`) | ||
for (const processedEnv of processedEnvs) { | ||
if (processedEnv.privateKeyAdded) { | ||
logger.success(`✔ key added to .env.keys (${processedEnv.privateKeyName})`) | ||
@@ -67,16 +73,7 @@ if (!isIgnoringDotenvKeys()) { | ||
logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx run -- yourcommand] to test decryption locally`) | ||
logger.help2(`ℹ run [${processedEnv.privateKeyName}='${processedEnv.privateKey}' dotenvx run -- yourcommand] to test decryption locally`) | ||
} | ||
} | ||
} catch (error) { | ||
logger.error(error.message) | ||
if (error.help) { | ||
logger.help(error.help) | ||
} | ||
if (error.debug) { | ||
logger.debug(error.debug) | ||
} | ||
if (error.code) { | ||
logger.debug(`ERROR_CODE: ${error.code}`) | ||
} | ||
catchAndLog(error) | ||
process.exit(1) | ||
@@ -83,0 +80,0 @@ } |
const fsx = require('./../../lib/helpers/fsx') | ||
const { logger } = require('./../../shared/logger') | ||
const main = require('./../../lib/main') | ||
const Sets = require('./../../lib/services/sets') | ||
const catchAndLog = require('../../lib/helpers/catchAndLog') | ||
const isIgnoringDotenvKeys = require('../../lib/helpers/isIgnoringDotenvKeys') | ||
@@ -22,7 +23,9 @@ | ||
try { | ||
const envs = this.envs | ||
const { | ||
processedEnvFiles, | ||
processedEnvs, | ||
changedFilepaths, | ||
unchangedFilepaths | ||
} = main.set(key, value, options.envFile, encrypt) | ||
} = new Sets(key, value, envs, encrypt).run() | ||
@@ -35,17 +38,20 @@ let withEncryption = '' | ||
for (const processedEnvFile of processedEnvFiles) { | ||
logger.verbose(`setting for ${processedEnvFile.envFilepath}`) | ||
for (const processedEnv of processedEnvs) { | ||
logger.verbose(`setting for ${processedEnv.envFilepath}`) | ||
if (processedEnvFile.error) { | ||
if (processedEnvFile.error.code === 'MISSING_ENV_FILE') { | ||
logger.warn(processedEnvFile.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnvFile.envFilepath}] and re-run [dotenvx set]`) | ||
if (processedEnv.error) { | ||
if (processedEnv.error.code === 'MISSING_ENV_FILE') { | ||
logger.warn(processedEnv.error.message) | ||
logger.help(`? add one with [echo "HELLO=World" > ${processedEnv.envFilepath}] and re-run [dotenvx set]`) | ||
} else { | ||
logger.warn(processedEnvFile.error.message) | ||
logger.warn(processedEnv.error.message) | ||
if (processedEnv.error.help) { | ||
logger.help(processedEnv.error.help) | ||
} | ||
} | ||
} else { | ||
fsx.writeFileX(processedEnvFile.filepath, processedEnvFile.envSrc) | ||
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc) | ||
logger.verbose(`${processedEnvFile.key} set${withEncryption} (${processedEnvFile.envFilepath})`) | ||
logger.debug(`${processedEnvFile.key} set${withEncryption} to ${processedEnvFile.value} (${processedEnvFile.envFilepath})`) | ||
logger.verbose(`${processedEnv.key} set${withEncryption} (${processedEnv.envFilepath})`) | ||
logger.debug(`${processedEnv.key} set${withEncryption} to ${processedEnv.value} (${processedEnv.envFilepath})`) | ||
} | ||
@@ -62,5 +68,5 @@ } | ||
for (const processedEnvFile of processedEnvFiles) { | ||
if (processedEnvFile.privateKeyAdded) { | ||
logger.success(`✔ key added to .env.keys (${processedEnvFile.privateKeyName})`) | ||
for (const processedEnv of processedEnvs) { | ||
if (processedEnv.privateKeyAdded) { | ||
logger.success(`✔ key added to .env.keys (${processedEnv.privateKeyName})`) | ||
@@ -71,16 +77,7 @@ if (!isIgnoringDotenvKeys()) { | ||
logger.help2(`ℹ run [${processedEnvFile.privateKeyName}='${processedEnvFile.privateKey}' dotenvx get ${key}] to test decryption locally`) | ||
logger.help2(`ℹ run [${processedEnv.privateKeyName}='${processedEnv.privateKey}' dotenvx get ${key}] to test decryption locally`) | ||
} | ||
} | ||
} catch (error) { | ||
logger.error(error.message) | ||
if (error.help) { | ||
logger.help(error.help) | ||
} | ||
if (error.debug) { | ||
logger.debug(error.debug) | ||
} | ||
if (error.code) { | ||
logger.debug(`ERROR_CODE: ${error.code}`) | ||
} | ||
catchAndLog(error) | ||
process.exit(1) | ||
@@ -87,0 +84,0 @@ } |
@@ -62,3 +62,2 @@ #!/usr/bin/env node | ||
this.envs = envs | ||
runAction.apply(this, args) | ||
@@ -82,3 +81,2 @@ }) | ||
this.envs = envs | ||
getAction.apply(this, args) | ||
@@ -95,6 +93,9 @@ }) | ||
.argument('value', 'value') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), []) | ||
.option('-c, --encrypt', 'encrypt value (default: true)', true) | ||
.option('-p, --plain', 'store value as plain text', false) | ||
.action(setAction) | ||
.action(function (...args) { | ||
this.envs = envs | ||
setAction.apply(this, args) | ||
}) | ||
@@ -105,7 +106,10 @@ // dotenvx encrypt | ||
.description('convert .env file(s) to encrypted .env file(s)') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), []) | ||
.option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)') | ||
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)') | ||
.option('--stdout', 'send to stdout') | ||
.action(encryptAction) | ||
.action(function (...args) { | ||
this.envs = envs | ||
encryptAction.apply(this, args) | ||
}) | ||
@@ -116,7 +120,10 @@ // dotenvx decrypt | ||
.description('convert encrypted .env file(s) to plain .env file(s)') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)') | ||
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), []) | ||
.option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)') | ||
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)') | ||
.option('--stdout', 'send to stdout') | ||
.action(decryptAction) | ||
.action(function (...args) { | ||
this.envs = envs | ||
decryptAction.apply(this, args) | ||
}) | ||
@@ -123,0 +130,0 @@ // dotenvx keypair |
const { decrypt } = require('eciesjs') | ||
const truncate = require('./truncate') | ||
const PREFIX = 'encrypted:' | ||
@@ -30,5 +32,5 @@ | ||
if (e.message === 'Invalid private key') { | ||
decryptionError = new Error('private key looks invalid') | ||
decryptionError = new Error(`private key [${truncate(privateKey)}] looks invalid`) | ||
} else if (e.message === 'Unsupported state or unable to authenticate data') { | ||
decryptionError = new Error('private key looks wrong') | ||
decryptionError = new Error(`private key [${truncate(privateKey)}] looks wrong`) | ||
} else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') { | ||
@@ -35,0 +37,0 @@ decryptionError = new Error('encrypted data looks malformed') |
const ENCRYPTION_PATTERN = /^encrypted:.+/ | ||
function isEncrypted (key, value) { | ||
function isEncrypted (value) { | ||
return ENCRYPTION_PATTERN.test(value) | ||
@@ -5,0 +5,0 @@ } |
@@ -10,3 +10,3 @@ const dotenv = require('dotenv') | ||
for (const [key, value] of Object.entries(parsed)) { | ||
const result = isEncrypted(key, value) || isPublicKey(key, value) | ||
const result = isEncrypted(value) || isPublicKey(key, value) | ||
if (!result) { | ||
@@ -13,0 +13,0 @@ return false |
@@ -13,6 +13,3 @@ // @ts-check | ||
const Run = require('./services/run') | ||
const Sets = require('./services/sets') | ||
const Keypair = require('./services/keypair') | ||
const Encrypt = require('./services/encrypt') | ||
const Decrypt = require('./services/decrypt') | ||
const Genexample = require('./services/genexample') | ||
@@ -200,17 +197,2 @@ | ||
/** @type {import('./main').set} */ | ||
const set = function (key, value, envFile, encrypt) { | ||
return new Sets(key, value, envFile, encrypt).run() | ||
} | ||
/** @type {import('./main').encrypt} */ | ||
const encrypt = function (envFile, key, excludeKey) { | ||
return new Encrypt(envFile, key, excludeKey).run() | ||
} | ||
/** @type {import('./main').encrypt} */ | ||
const decrypt = function (envFile, key, excludeKey) { | ||
return new Decrypt(envFile, key, excludeKey).run() | ||
} | ||
/** @type {import('./main').keypair} */ | ||
@@ -227,7 +209,4 @@ const keypair = function (envFile, key) { | ||
// actions related | ||
encrypt, | ||
decrypt, | ||
ls, | ||
get, | ||
set, | ||
keypair, | ||
@@ -234,0 +213,0 @@ genexample, |
@@ -6,19 +6,19 @@ const fsx = require('./../helpers/fsx') | ||
const smartDotenvPrivateKey = require('./../helpers/smartDotenvPrivateKey') | ||
const TYPE_ENV_FILE = 'envFile' | ||
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName') | ||
const findPrivateKey = require('./../helpers/findPrivateKey') | ||
const decryptValue = require('./../helpers/decryptValue') | ||
const isEncrypted = require('./../helpers/isEncrypted') | ||
const replace = require('./../helpers/replace') | ||
const detectEncoding = require('./../helpers/detectEncoding') | ||
const determineEnvs = require('./../helpers/determineEnvs') | ||
class Decrypt { | ||
/** | ||
* @param {string|string[]} [envFile] | ||
* @param {string|string[]} [key] | ||
* @param {string|string[]} [excludeKey] | ||
**/ | ||
constructor (envFile = '.env', key = [], excludeKey = []) { | ||
this.envFile = envFile | ||
constructor (envs = [], key = [], excludeKey = []) { | ||
this.envs = determineEnvs(envs, process.env) | ||
this.key = key | ||
this.excludeKey = excludeKey | ||
this.processedEnvFiles = [] | ||
this.processedEnvs = [] | ||
this.changedFilepaths = new Set() | ||
@@ -29,87 +29,88 @@ this.unchangedFilepaths = new Set() | ||
run () { | ||
const envFilepaths = this._envFilepaths() | ||
const keys = this._keys() | ||
// example | ||
// envs [ | ||
// { type: 'envFile', value: '.env' } | ||
// ] | ||
this.keys = this._keys() | ||
const excludeKeys = this._excludeKeys() | ||
const exclude = picomatch(excludeKeys) | ||
const include = picomatch(keys, { ignore: excludeKeys }) | ||
for (const envFilepath of envFilepaths) { | ||
const filepath = path.resolve(envFilepath) | ||
this.exclude = picomatch(excludeKeys) | ||
this.include = picomatch(this.keys, { ignore: excludeKeys }) | ||
const row = {} | ||
row.keys = [] | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
for (const env of this.envs) { | ||
if (env.type === TYPE_ENV_FILE) { | ||
this._decryptEnvFile(env.value) | ||
} | ||
} | ||
try { | ||
// get the src | ||
let src = fsx.readFileX(filepath) | ||
return { | ||
processedEnvs: this.processedEnvs, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
} | ||
} | ||
// if DOTENV_PRIVATE_KEY_* already set in process.env then use it | ||
const privateKey = smartDotenvPrivateKey(envFilepath) | ||
row.privateKey = privateKey | ||
row.privateKeyName = guessPrivateKeyName(filepath) | ||
_decryptEnvFile (envFilepath) { | ||
const row = {} | ||
row.keys = [] | ||
row.type = TYPE_ENV_FILE | ||
// track possible changes | ||
row.changed = false | ||
const filepath = path.resolve(envFilepath) | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
// iterate over all non-encrypted values and encrypt them | ||
const parsed = dotenv.parse(src) | ||
for (const [key, value] of Object.entries(parsed)) { | ||
// key excluded - don't decrypt it | ||
if (exclude(key)) { | ||
continue | ||
} | ||
try { | ||
const encoding = this._detectEncoding(filepath) | ||
let envSrc = fsx.readFileX(filepath, { encoding }) | ||
const envParsed = dotenv.parse(envSrc) | ||
// key effectively excluded (by not being in the list of includes) - don't encrypt it | ||
if (keys.length > 0 && !include(key)) { | ||
continue | ||
} | ||
const privateKey = findPrivateKey(envFilepath) | ||
const privateKeyName = guessPrivateKeyName(envFilepath) | ||
const encrypted = isEncrypted(key, value) | ||
if (encrypted) { | ||
row.keys.push(key) // track key(s) | ||
row.privateKey = privateKey | ||
row.privateKeyName = privateKeyName | ||
row.changed = false // track possible changes | ||
const decryptedValue = decryptValue(value, privateKey) | ||
// once newSrc is built write it out | ||
src = replace(src, key, decryptedValue) | ||
row.changed = true // track change | ||
} | ||
for (const [key, value] of Object.entries(envParsed)) { | ||
// key excluded - don't decrypt it | ||
if (this.exclude(key)) { | ||
continue | ||
} | ||
if (row.changed) { | ||
row.envSrc = src | ||
this.changedFilepaths.add(envFilepath) | ||
} else { | ||
row.envSrc = src | ||
this.unchangedFilepaths.add(envFilepath) | ||
// key effectively excluded (by not being in the list of includes) - don't decrypt it | ||
if (this.keys.length > 0 && !this.include(key)) { | ||
continue | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
row.error = error | ||
} else { | ||
row.error = e | ||
const encrypted = isEncrypted(value) | ||
if (encrypted) { | ||
row.keys.push(key) // track key(s) | ||
const decryptedValue = decryptValue(value, privateKey) | ||
// once newSrc is built write it out | ||
envSrc = replace(envSrc, key, decryptedValue) | ||
row.changed = true // track change | ||
} | ||
} | ||
this.processedEnvFiles.push(row) | ||
} | ||
row.envSrc = envSrc | ||
if (row.changed) { | ||
this.changedFilepaths.add(envFilepath) | ||
} else { | ||
this.unchangedFilepaths.add(envFilepath) | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
return { | ||
processedEnvFiles: this.processedEnvFiles, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
row.error = error | ||
} else { | ||
row.error = e | ||
} | ||
} | ||
} | ||
_envFilepaths () { | ||
if (!Array.isArray(this.envFile)) { | ||
return [this.envFile] | ||
} | ||
return this.envFile | ||
this.processedEnvs.push(row) | ||
} | ||
@@ -132,4 +133,8 @@ | ||
} | ||
_detectEncoding (filepath) { | ||
return detectEncoding(filepath) | ||
} | ||
} | ||
module.exports = Decrypt |
@@ -6,20 +6,24 @@ const fsx = require('./../helpers/fsx') | ||
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey') | ||
const TYPE_ENV_FILE = 'envFile' | ||
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName') | ||
const guessPublicKeyName = require('./../helpers/guessPublicKeyName') | ||
const encryptValue = require('./../helpers/encryptValue') | ||
const isEncrypted = require('./../helpers/isEncrypted') | ||
const replace = require('./../helpers/replace') | ||
const detectEncoding = require('./../helpers/detectEncoding') | ||
const determineEnvs = require('./../helpers/determineEnvs') | ||
const findPrivateKey = require('./../helpers/findPrivateKey') | ||
const findPublicKey = require('./../helpers/findPublicKey') | ||
const keyPair = require('./../helpers/keyPair') | ||
const truncate = require('./../helpers/truncate') | ||
const isPublicKey = require('./../helpers/isPublicKey') | ||
const replace = require('./../helpers/replace') | ||
class Encrypt { | ||
/** | ||
* @param {string|string[]} [envFile] | ||
* @param {string|string[]} [key] | ||
* @param {string|string[]} [excludeKey] | ||
**/ | ||
constructor (envFile = '.env', key = [], excludeKey = []) { | ||
this.envFile = envFile | ||
constructor (envs = [], key = [], excludeKey = []) { | ||
this.envs = determineEnvs(envs, process.env) | ||
this.key = key | ||
this.excludeKey = excludeKey | ||
this.processedEnvFiles = [] | ||
this.processedEnvs = [] | ||
this.changedFilepaths = new Set() | ||
@@ -30,100 +34,163 @@ this.unchangedFilepaths = new Set() | ||
run () { | ||
const envFilepaths = this._envFilepaths() | ||
const keys = this._keys() | ||
// example | ||
// envs [ | ||
// { type: 'envFile', value: '.env' } | ||
// ] | ||
this.keys = this._keys() | ||
const excludeKeys = this._excludeKeys() | ||
const exclude = picomatch(excludeKeys) | ||
const include = picomatch(keys, { ignore: excludeKeys }) | ||
for (const envFilepath of envFilepaths) { | ||
const filepath = path.resolve(envFilepath) | ||
this.exclude = picomatch(excludeKeys) | ||
this.include = picomatch(this.keys, { ignore: excludeKeys }) | ||
const row = {} | ||
row.keys = [] | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
for (const env of this.envs) { | ||
if (env.type === TYPE_ENV_FILE) { | ||
this._encryptEnvFile(env.value) | ||
} | ||
} | ||
try { | ||
// get the original src | ||
let src = fsx.readFileX(filepath) | ||
// get/generate the public key | ||
return { | ||
processedEnvs: this.processedEnvs, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
} | ||
} | ||
_encryptEnvFile (envFilepath) { | ||
const row = {} | ||
row.keys = [] | ||
row.type = TYPE_ENV_FILE | ||
const filename = path.basename(envFilepath) | ||
const filepath = path.resolve(envFilepath) | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
try { | ||
const encoding = this._detectEncoding(filepath) | ||
let envSrc = fsx.readFileX(filepath, { encoding }) | ||
const envParsed = dotenv.parse(envSrc) | ||
let publicKey | ||
let privateKey | ||
const publicKeyName = guessPublicKeyName(envFilepath) | ||
const privateKeyName = guessPrivateKeyName(envFilepath) | ||
const existingPrivateKey = findPrivateKey(envFilepath) | ||
const existingPublicKey = findPublicKey(envFilepath) | ||
if (existingPrivateKey) { | ||
const kp = keyPair(existingPrivateKey) | ||
publicKey = kp.publicKey | ||
privateKey = kp.privateKey | ||
// if derivation doesn't match what's in the file (or preset in env) | ||
if (existingPublicKey && existingPublicKey !== publicKey) { | ||
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`) | ||
error.code = 'INVALID_DOTENV_PRIVATE_KEY' | ||
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})` | ||
throw error | ||
} | ||
} else if (existingPublicKey) { | ||
publicKey = existingPublicKey | ||
} else { | ||
// .env.keys | ||
let keysSrc = '' | ||
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys') | ||
const { | ||
envSrc, | ||
keysSrc, | ||
publicKey, | ||
privateKey, | ||
publicKeyAdded, | ||
privateKeyAdded | ||
} = findOrCreatePublicKey(filepath, envKeysFilepath) | ||
if (fsx.existsSync(envKeysFilepath)) { | ||
keysSrc = fsx.readFileX(envKeysFilepath) | ||
} | ||
// handle .env.keys write | ||
fsx.writeFileX(envKeysFilepath, keysSrc) | ||
// preserve shebang | ||
const [firstLine, ...remainingLines] = envSrc.split('\n') | ||
let firstLinePreserved = '' | ||
if (firstLine.startsWith('#!')) { | ||
firstLinePreserved = firstLine + '\n' | ||
envSrc = remainingLines.join('\n') | ||
} | ||
src = envSrc // src was potentially modified by findOrCreatePublicKey so we set it again here | ||
const kp = keyPair() // generates a fresh keypair in memory | ||
publicKey = kp.publicKey | ||
privateKey = kp.privateKey | ||
row.changed = publicKeyAdded // track change | ||
row.publicKey = publicKey | ||
row.privateKey = privateKey | ||
row.privateKeyAdded = privateKeyAdded | ||
row.privateKeyName = guessPrivateKeyName(filepath) | ||
// publicKey | ||
const prependPublicKey = [ | ||
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/', | ||
'#/ public-key encryption for .env files /', | ||
'#/ [how it works](https://dotenvx.com/encryption) /', | ||
'#/----------------------------------------------------------/', | ||
`${publicKeyName}="${publicKey}"`, | ||
'', | ||
`# ${filename}` | ||
].join('\n') | ||
// iterate over all non-encrypted values and encrypt them | ||
const parsed = dotenv.parse(src) | ||
for (const [key, value] of Object.entries(parsed)) { | ||
// key excluded - don't encrypt it | ||
if (exclude(key)) { | ||
continue | ||
} | ||
// privateKey | ||
const firstTimeKeysSrc = [ | ||
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/', | ||
'#/ private decryption keys. DO NOT commit to source control /', | ||
'#/ [how it works](https://dotenvx.com/encryption) /', | ||
'#/----------------------------------------------------------/' | ||
].join('\n') | ||
const appendPrivateKey = [ | ||
`# ${filename}`, | ||
`${privateKeyName}="${privateKey}"`, | ||
'' | ||
].join('\n') | ||
// key effectively excluded (by not being in the list of includes) - don't encrypt it | ||
if (keys.length > 0 && !include(key)) { | ||
continue | ||
} | ||
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}` | ||
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n` | ||
keysSrc = `${keysSrc}\n${appendPrivateKey}` | ||
const encrypted = isEncrypted(key, value) || isPublicKey(key, value) | ||
if (!encrypted) { | ||
row.keys.push(key) // track key(s) | ||
// write to .env.keys | ||
fsx.writeFileX(envKeysFilepath, keysSrc) | ||
const encryptedValue = encryptValue(value, publicKey) | ||
// once newSrc is built write it out | ||
src = replace(src, key, encryptedValue) | ||
row.privateKeyAdded = true | ||
} | ||
row.changed = true // track change | ||
} | ||
row.publicKey = publicKey | ||
row.privateKey = privateKey | ||
row.privateKeyName = privateKeyName | ||
// iterate over all non-encrypted values and encrypt them | ||
for (const [key, value] of Object.entries(envParsed)) { | ||
// key excluded - don't encrypt it | ||
if (this.exclude(key)) { | ||
continue | ||
} | ||
if (row.changed) { | ||
row.envSrc = src | ||
this.changedFilepaths.add(envFilepath) | ||
} else { | ||
row.envSrc = src | ||
this.unchangedFilepaths.add(envFilepath) | ||
// key effectively excluded (by not being in the list of includes) - don't encrypt it | ||
if (this.keys.length > 0 && !this.include(key)) { | ||
continue | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
row.error = error | ||
} else { | ||
row.error = e | ||
const encrypted = isEncrypted(value) || isPublicKey(key, value) | ||
if (!encrypted) { | ||
row.keys.push(key) // track key(s) | ||
const encryptedValue = encryptValue(value, publicKey) | ||
// once newSrc is built write it out | ||
envSrc = replace(envSrc, key, encryptedValue) | ||
row.changed = true // track change | ||
} | ||
} | ||
this.processedEnvFiles.push(row) | ||
} | ||
row.envSrc = envSrc | ||
if (row.changed) { | ||
this.changedFilepaths.add(envFilepath) | ||
} else { | ||
this.unchangedFilepaths.add(envFilepath) | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
return { | ||
processedEnvFiles: this.processedEnvFiles, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
row.error = error | ||
} else { | ||
row.error = e | ||
} | ||
} | ||
} | ||
_envFilepaths () { | ||
if (!Array.isArray(this.envFile)) { | ||
return [this.envFile] | ||
} | ||
return this.envFile | ||
this.processedEnvs.push(row) | ||
} | ||
@@ -146,4 +213,8 @@ | ||
} | ||
_detectEncoding (filepath) { | ||
return detectEncoding(filepath) | ||
} | ||
} | ||
module.exports = Encrypt |
const fsx = require('./../helpers/fsx') | ||
const path = require('path') | ||
const dotenv = require('dotenv') | ||
const childProcess = require('child_process') | ||
@@ -9,4 +8,2 @@ const TYPE_ENV = 'env' | ||
const TYPE_ENV_VAULT_FILE = 'envVaultFile' | ||
const DEFAULT_ENVS = [{ type: TYPE_ENV_FILE, value: '.env' }] | ||
const DEFAULT_ENV_VAULTS = [{ type: TYPE_ENV_VAULT_FILE, value: '.env.vault' }] | ||
@@ -17,12 +14,9 @@ const inject = require('./../helpers/inject') | ||
const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey') | ||
const guessPrivateKeyFilename = require('./../helpers/guessPrivateKeyFilename') | ||
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName') | ||
const detectEncoding = require('./../helpers/detectEncoding') | ||
const findPrivateKey = require('./../helpers/findPrivateKey') | ||
const determineEnvs = require('./../helpers/determineEnvs') | ||
const Keypair = require('./../services/keypair') | ||
class Run { | ||
constructor (envs = [], overload = false, DOTENV_KEY = '', processEnv = process.env) { | ||
this.dotenvPrivateKeyNames = Object.keys(processEnv).filter(key => key.startsWith('DOTENV_PRIVATE_KEY')) // important, must be first. used by determineEnvs | ||
this.envs = this._determineEnvs(envs, DOTENV_KEY) | ||
this.envs = determineEnvs(envs, processEnv, DOTENV_KEY) | ||
this.overload = overload | ||
@@ -48,3 +42,3 @@ this.DOTENV_KEY = DOTENV_KEY | ||
for (const env of this.envs) { | ||
if (env.type === TYPE_ENV_VAULT_FILE) { | ||
if (env.type === TYPE_ENV_VAULT_FILE) { // deprecate someday - for deprecated .env.vault files | ||
this._injectEnvVaultFile(env.value) | ||
@@ -102,3 +96,3 @@ } else if (env.type === TYPE_ENV_FILE) { | ||
const privateKey = this._determinePrivateKey(envFilepath) | ||
const privateKey = findPrivateKey(envFilepath) | ||
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv) | ||
@@ -196,57 +190,2 @@ row.parsed = parsed | ||
_determineEnvsFromDotenvPrivateKey () { | ||
const envs = [] | ||
for (const privateKeyName of this.dotenvPrivateKeyNames) { | ||
const filename = guessPrivateKeyFilename(privateKeyName) | ||
envs.push({ type: TYPE_ENV_FILE, value: filename }) | ||
} | ||
return envs | ||
} | ||
_determineEnvs (envs = [], DOTENV_KEY = '') { | ||
if (!envs || envs.length <= 0) { | ||
// if process.env.DOTENV_PRIVATE_KEY or process.env.DOTENV_PRIVATE_KEY_${environment} is set, assume inline encryption methodology | ||
if (this.dotenvPrivateKeyNames.length > 0) { | ||
return this._determineEnvsFromDotenvPrivateKey() | ||
} | ||
if (DOTENV_KEY.length > 0) { | ||
// if DOTENV_KEY is set then default to look for .env.vault file | ||
return DEFAULT_ENV_VAULTS | ||
} else { | ||
return DEFAULT_ENVS // default to .env file expectation | ||
} | ||
} else { | ||
let fileAlreadySpecified = false // can be .env or .env.vault type | ||
for (const env of envs) { | ||
// if DOTENV_KEY set then we are checking if a .env.vault file is already specified | ||
if (DOTENV_KEY.length > 0 && env.type === TYPE_ENV_VAULT_FILE) { | ||
fileAlreadySpecified = true | ||
} | ||
// if DOTENV_KEY not set then we are checking if a .env file is already specified | ||
if (DOTENV_KEY.length <= 0 && env.type === TYPE_ENV_FILE) { | ||
fileAlreadySpecified = true | ||
} | ||
} | ||
// return early since envs array objects already contain 1 .env.vault or .env file | ||
if (fileAlreadySpecified) { | ||
return envs | ||
} | ||
// no .env.vault or .env file specified as a flag so we assume either .env.vault (if dotenv key is set) or a .env file | ||
if (DOTENV_KEY.length > 0) { | ||
// if DOTENV_KEY is set then default to look for .env.vault file | ||
return [...DEFAULT_ENV_VAULTS, ...envs] | ||
} else { | ||
// if no DOTENV_KEY then default to look for .env file | ||
return [...DEFAULT_ENVS, ...envs] | ||
} | ||
} | ||
} | ||
// handle scenario for comma separated keys - for use with key rotation | ||
@@ -279,27 +218,4 @@ // example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod" | ||
} | ||
_determinePrivateKey (envFilepath) { | ||
const privateKeyName = guessPrivateKeyName(envFilepath) | ||
let privateKey | ||
try { | ||
// if installed as sibling module | ||
const projectRoot = path.resolve(process.cwd()) | ||
const dotenvxProPath = require.resolve('@dotenvx/dotenvx-pro', { paths: [projectRoot] }) | ||
const { keypair } = require(dotenvxProPath) | ||
privateKey = keypair(envFilepath, privateKeyName) | ||
} catch (_e) { | ||
try { | ||
// if installed as binary cli | ||
privateKey = childProcess.execSync(`dotenvx-pro keypair ${privateKeyName} -f ${envFilepath}`, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim() | ||
} catch (_e) { | ||
// fallback to local KeyPair - smart enough to handle process.env, .env.keys, etc | ||
privateKey = new Keypair(envFilepath, privateKeyName).run() | ||
} | ||
} | ||
return privateKey | ||
} | ||
} | ||
module.exports = Run |
const fsx = require('./../helpers/fsx') | ||
const path = require('path') | ||
const dotenv = require('dotenv') | ||
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey') | ||
const TYPE_ENV_FILE = 'envFile' | ||
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName') | ||
const guessPublicKeyName = require('./../helpers/guessPublicKeyName') | ||
const encryptValue = require('./../helpers/encryptValue') | ||
const decryptValue = require('./../helpers/decryptValue') | ||
const replace = require('./../helpers/replace') | ||
const detectEncoding = require('./../helpers/detectEncoding') | ||
const determineEnvs = require('./../helpers/determineEnvs') | ||
const findPrivateKey = require('./../helpers/findPrivateKey') | ||
const findPublicKey = require('./../helpers/findPublicKey') | ||
const keyPair = require('./../helpers/keyPair') | ||
const truncate = require('./../helpers/truncate') | ||
const isEncrypted = require('./../helpers/isEncrypted') | ||
class Sets { | ||
constructor (key, value, envFile = '.env', encrypt = true) { | ||
constructor (key, value, envs = [], encrypt = true) { | ||
this.envs = determineEnvs(envs, process.env) | ||
this.key = key | ||
this.value = value | ||
this.envFile = envFile | ||
this.encrypt = encrypt | ||
this.processedEnvFiles = [] | ||
this.processedEnvs = [] | ||
this.changedFilepaths = new Set() | ||
this.unchangedFilepaths = new Set() | ||
this.readableFilepaths = new Set() | ||
} | ||
run () { | ||
const envFilepaths = this._envFilepaths() | ||
for (const envFilepath of envFilepaths) { | ||
const filepath = path.resolve(envFilepath) | ||
// example | ||
// envs [ | ||
// { type: 'envFile', value: '.env' } | ||
// ] | ||
const row = {} | ||
row.key = this.key | ||
row.value = this.value | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
row.changed = false | ||
for (const env of this.envs) { | ||
if (env.type === TYPE_ENV_FILE) { | ||
this._setEnvFile(env.value) | ||
} | ||
} | ||
try { | ||
let value = this.value | ||
let src = fsx.readFileX(filepath) | ||
row.originalValue = dotenv.parse(src)[row.key] || null | ||
return { | ||
processedEnvs: this.processedEnvs, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
} | ||
} | ||
if (this.encrypt) { | ||
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys') | ||
const { | ||
envSrc, | ||
keysSrc, | ||
publicKey, | ||
privateKey, | ||
publicKeyAdded, | ||
privateKeyAdded | ||
} = findOrCreatePublicKey(filepath, envKeysFilepath) | ||
_setEnvFile (envFilepath) { | ||
const row = {} | ||
row.key = this.key || null | ||
row.value = this.value || null | ||
row.type = TYPE_ENV_FILE | ||
// handle .env.keys write | ||
fsx.writeFileX(envKeysFilepath, keysSrc) | ||
const filename = path.basename(envFilepath) | ||
const filepath = path.resolve(envFilepath) | ||
row.filepath = filepath | ||
row.envFilepath = envFilepath | ||
row.changed = false | ||
src = envSrc // src was potentially modified by findOrCreatePublicKey so we set it again here | ||
try { | ||
const encoding = this._detectEncoding(filepath) | ||
let envSrc = fsx.readFileX(filepath, { encoding }) | ||
const envParsed = dotenv.parse(envSrc) | ||
row.originalValue = envParsed[row.key] || null | ||
const wasPlainText = !isEncrypted(row.originalValue) | ||
this.readableFilepaths.add(envFilepath) | ||
value = encryptValue(value, publicKey) | ||
if (this.encrypt) { | ||
let publicKey | ||
let privateKey | ||
row.changed = publicKeyAdded // track change | ||
row.encryptedValue = value | ||
row.publicKey = publicKey | ||
row.privateKey = privateKey | ||
row.privateKeyAdded = privateKeyAdded | ||
row.privateKeyName = guessPrivateKeyName(filepath) | ||
} | ||
const publicKeyName = guessPublicKeyName(envFilepath) | ||
const privateKeyName = guessPrivateKeyName(envFilepath) | ||
const existingPrivateKey = findPrivateKey(envFilepath) | ||
const existingPublicKey = findPublicKey(envFilepath) | ||
if (value !== row.originalValue) { | ||
row.envSrc = replace(src, this.key, value) | ||
if (existingPrivateKey) { | ||
const kp = keyPair(existingPrivateKey) | ||
publicKey = kp.publicKey | ||
privateKey = kp.privateKey | ||
this.changedFilepaths.add(envFilepath) | ||
row.changed = true | ||
if (row.originalValue) { | ||
row.originalValue = decryptValue(row.originalValue, privateKey) | ||
} | ||
// if derivation doesn't match what's in the file (or preset in env) | ||
if (existingPublicKey && existingPublicKey !== publicKey) { | ||
const error = new Error(`derived public key (${truncate(publicKey)}) does not match the existing public key (${truncate(existingPublicKey)})`) | ||
error.code = 'INVALID_DOTENV_PRIVATE_KEY' | ||
error.help = `debug info: ${privateKeyName}=${truncate(existingPrivateKey)} (derived ${publicKeyName}=${truncate(publicKey)} vs existing ${publicKeyName}=${truncate(existingPublicKey)})` | ||
throw error | ||
} | ||
} else if (existingPublicKey) { | ||
publicKey = existingPublicKey | ||
} else { | ||
row.envSrc = src | ||
// .env.keys | ||
let keysSrc = '' | ||
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys') | ||
if (fsx.existsSync(envKeysFilepath)) { | ||
keysSrc = fsx.readFileX(envKeysFilepath) | ||
} | ||
this.unchangedFilepaths.add(envFilepath) | ||
// preserve shebang | ||
const [firstLine, ...remainingLines] = envSrc.split('\n') | ||
let firstLinePreserved = '' | ||
if (firstLine.startsWith('#!')) { | ||
firstLinePreserved = firstLine + '\n' | ||
envSrc = remainingLines.join('\n') | ||
} | ||
const kp = keyPair() // generates a fresh keypair in memory | ||
publicKey = kp.publicKey | ||
privateKey = kp.privateKey | ||
// publicKey | ||
const prependPublicKey = [ | ||
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/', | ||
'#/ public-key encryption for .env files /', | ||
'#/ [how it works](https://dotenvx.com/encryption) /', | ||
'#/----------------------------------------------------------/', | ||
`${publicKeyName}="${publicKey}"`, | ||
'', | ||
`# ${filename}` | ||
].join('\n') | ||
// privateKey | ||
const firstTimeKeysSrc = [ | ||
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/', | ||
'#/ private decryption keys. DO NOT commit to source control /', | ||
'#/ [how it works](https://dotenvx.com/encryption) /', | ||
'#/----------------------------------------------------------/' | ||
].join('\n') | ||
const appendPrivateKey = [ | ||
`# ${filename}`, | ||
`${privateKeyName}="${privateKey}"`, | ||
'' | ||
].join('\n') | ||
envSrc = `${firstLinePreserved}${prependPublicKey}\n${envSrc}` | ||
keysSrc = keysSrc.length > 1 ? keysSrc : `${firstTimeKeysSrc}\n` | ||
keysSrc = `${keysSrc}\n${appendPrivateKey}` | ||
// write to .env.keys | ||
fsx.writeFileX(envKeysFilepath, keysSrc) | ||
row.privateKeyAdded = true | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
row.error = error | ||
} else { | ||
row.error = e | ||
} | ||
row.publicKey = publicKey | ||
row.privateKey = privateKey | ||
row.encryptedValue = encryptValue(this.value, publicKey) | ||
row.privateKeyName = privateKeyName | ||
} | ||
this.processedEnvFiles.push(row) | ||
const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt | ||
const valueChanged = this.value !== row.originalValue | ||
if (goingFromPlainTextToEncrypted || valueChanged) { | ||
row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value) | ||
this.changedFilepaths.add(envFilepath) | ||
row.changed = true | ||
} else { | ||
row.envSrc = envSrc | ||
this.unchangedFilepaths.add(envFilepath) | ||
row.changed = false | ||
} | ||
} catch (e) { | ||
if (e.code === 'ENOENT') { | ||
const error = new Error(`missing ${envFilepath} file (${filepath})`) | ||
error.code = 'MISSING_ENV_FILE' | ||
row.error = error | ||
} else { | ||
row.error = e | ||
} | ||
} | ||
return { | ||
processedEnvFiles: this.processedEnvFiles, | ||
changedFilepaths: [...this.changedFilepaths], | ||
unchangedFilepaths: [...this.unchangedFilepaths] | ||
} | ||
this.processedEnvs.push(row) | ||
} | ||
_envFilepaths () { | ||
if (!Array.isArray(this.envFile)) { | ||
return [this.envFile] | ||
} | ||
return this.envFile | ||
_detectEncoding (filepath) { | ||
return detectEncoding(filepath) | ||
} | ||
@@ -104,0 +187,0 @@ } |
@@ -10,3 +10,5 @@ const depth = require('../lib/helpers/colorDepth') | ||
['plum', 35], // mapped to magenta | ||
['red', 31] | ||
['red', 31], | ||
['electricblue', 36], | ||
['dodgerblue', 36] | ||
]) | ||
@@ -21,3 +23,5 @@ | ||
['plum', 182], | ||
['red', 196] | ||
['red', 196], | ||
['electricblue', 45], | ||
['dodgerblue', 33] | ||
]) | ||
@@ -24,0 +28,0 @@ |
@@ -31,3 +31,3 @@ const packageJson = require('../lib/helpers/packageJson') | ||
const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv | ||
const help = getColor('blue') | ||
const help = getColor('dodgerblue') | ||
const help2 = getColor('gray') | ||
@@ -34,0 +34,0 @@ const verbose = getColor('plum') |
213511
81
3616
30
7