修改后台权限

This commit is contained in:
yoyuzh
2026-03-24 14:30:59 +08:00
parent 00f902f475
commit b2d9db7be9
9310 changed files with 1246063 additions and 48 deletions

View File

@@ -0,0 +1,77 @@
const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const Decrypt = require('./../../lib/services/decrypt')
const catchAndLog = require('../../lib/helpers/catchAndLog')
function decrypt () {
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const envs = this.envs
const opsOn = options.opsOff !== true
let errorCount = 0
// stdout - should not have a try so that exit codes can surface to stdout
if (options.stdout) {
const {
processedEnvs
} = new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
if (processedEnv.error) {
errorCount += 1
logger.error(processedEnv.error.messageWithHelp)
} else {
console.log(processedEnv.envSrc)
}
}
if (errorCount > 0) {
process.exit(1)
} else {
process.exit(0) // exit early
}
} else {
try {
const {
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = new Decrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
logger.verbose(`decrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
if (processedEnv.error) {
errorCount += 1
logger.error(processedEnv.error.messageWithHelp)
} else if (processedEnv.changed) {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`decrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`)
} else {
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`)
}
}
if (changedFilepaths.length > 0) {
logger.success(`◇ decrypted (${changedFilepaths.join(',')})`)
} else if (unchangedFilepaths.length > 0) {
logger.info(`○ no changes (${unchangedFilepaths})`)
} else {
// do nothing - scenario when no .env files found
}
if (errorCount > 0) {
process.exit(1)
}
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
}
module.exports = decrypt

View File

@@ -0,0 +1,73 @@
const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const Encrypt = require('./../../lib/services/encrypt')
const catchAndLog = require('../../lib/helpers/catchAndLog')
const localDisplayPath = require('../../lib/helpers/localDisplayPath')
function encrypt () {
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const envs = this.envs
const opsOn = options.opsOff !== true
// stdout - should not have a try so that exit codes can surface to stdout
if (options.stdout) {
const {
processedEnvs
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
console.log(processedEnv.envSrc)
}
process.exit(0) // exit early
} else {
try {
const {
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
if (processedEnv.error) {
logger.warn(processedEnv.error.messageWithHelp)
} else if (processedEnv.changed) {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`encrypted ${processedEnv.envFilepath} (${processedEnv.filepath})`)
} else {
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`)
}
}
if (changedFilepaths.length > 0) {
const keyAddedEnv = processedEnvs.find((processedEnv) => processedEnv.privateKeyAdded)
let msg = `◈ encrypted (${changedFilepaths.join(',')})`
if (keyAddedEnv) {
const envKeysFilepath = localDisplayPath(keyAddedEnv.envKeysFilepath)
msg += ` + key (${envKeysFilepath})`
}
logger.success(msg)
} else if (unchangedFilepaths.length > 0) {
logger.info(`○ no changes (${unchangedFilepaths})`)
} else {
// do nothing - scenario when no .env files found
}
for (const processedEnv of processedEnvs) {
if (processedEnv.privateKeyAdded) {
// intentionally quiet: success line already communicates key creation
}
}
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
}
module.exports = encrypt

View File

@@ -0,0 +1,36 @@
const fsx = require('./../../../lib/helpers/fsx')
const path = require('path')
const main = require('./../../../lib/main')
const { logger } = require('./../../../shared/logger')
const catchAndLog = require('./../../../lib/helpers/catchAndLog')
function genexample (directory) {
logger.debug(`directory: ${directory}`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
try {
const {
envExampleFile,
envFile,
exampleFilepath,
addedKeys
} = main.genexample(directory, options.envFile)
logger.verbose(`loading env from ${envFile}`)
fsx.writeFileX(exampleFilepath, envExampleFile)
if (addedKeys.length > 0) {
logger.success(`▣ generated (${path.basename(exampleFilepath)})`)
} else {
logger.info('○ no changes (.env.example)')
}
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
module.exports = genexample

View File

@@ -0,0 +1,105 @@
const fsx = require('./../../../lib/helpers/fsx')
const DEFAULT_PATTERNS = ['.env*']
const { logger } = require('./../../../shared/logger')
class Generic {
constructor (filename, patterns = DEFAULT_PATTERNS, touchFile = false) {
this.filename = filename
this.patterns = patterns
this.touchFile = touchFile
}
append (str) {
fsx.appendFileSync(this.filename, `\n${str}`)
}
run () {
const changedPatterns = []
if (!fsx.existsSync(this.filename)) {
if (this.touchFile === true && this.patterns.length > 0) {
fsx.writeFileX(this.filename, '')
} else {
return
}
}
const lines = fsx.readFileX(this.filename).split(/\r?\n/)
this.patterns.forEach(pattern => {
if (!lines.includes(pattern.trim())) {
this.append(pattern)
changedPatterns.push(pattern.trim())
}
})
if (changedPatterns.length > 0) {
logger.success(`▣ ignored ${this.patterns} (${this.filename})`)
} else {
logger.info(`○ no changes (${this.filename})`)
}
}
}
class Git {
constructor (patterns = DEFAULT_PATTERNS) {
this.patterns = patterns
}
run () {
logger.verbose('add to .gitignore')
new Generic('.gitignore', this.patterns, true).run()
}
}
class Docker {
constructor (patterns = DEFAULT_PATTERNS) {
this.patterns = patterns
}
run () {
logger.verbose('add to .dockerignore (if exists)')
new Generic('.dockerignore', this.patterns).run()
}
}
class Npm {
constructor (patterns = DEFAULT_PATTERNS) {
this.patterns = patterns
}
run () {
logger.verbose('add to .npmignore (if existing)')
new Generic('.npmignore', this.patterns).run()
}
}
class Vercel {
constructor (patterns = DEFAULT_PATTERNS) {
this.patterns = patterns
}
run () {
logger.verbose('add to .vercelignore (if existing)')
new Generic('.vercelignore', this.patterns).run()
}
}
function gitignore () {
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const patterns = options.pattern
new Git(patterns).run()
new Docker(patterns).run()
new Npm(patterns).run()
new Vercel(patterns).run()
}
module.exports = gitignore
module.exports.Git = Git
module.exports.Docker = Docker
module.exports.Npm = Npm
module.exports.Vercel = Vercel
module.exports.Generic = Generic

View File

@@ -0,0 +1,30 @@
const { logger } = require('./../../../shared/logger')
const Prebuild = require('./../../../lib/services/prebuild')
const catchAndLog = require('./../../../lib/helpers/catchAndLog')
function prebuild (directory) {
// debug args
logger.debug(`directory: ${directory}`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
try {
const {
successMessage,
warnings
} = new Prebuild(directory, options).run()
for (const warning of warnings) {
logger.warn(warning.messageWithHelp)
}
logger.success(successMessage)
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
module.exports = prebuild

View File

@@ -0,0 +1,30 @@
const { logger } = require('./../../../shared/logger')
const Precommit = require('./../../../lib/services/precommit')
const catchAndLog = require('./../../../lib/helpers/catchAndLog')
function precommit (directory) {
// debug args
logger.debug(`directory: ${directory}`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
try {
const {
successMessage,
warnings
} = new Precommit(directory, options).run()
for (const warning of warnings) {
logger.warn(warning.messageWithHelp)
}
logger.success(successMessage)
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
module.exports = precommit

View File

@@ -0,0 +1,34 @@
const childProcess = require('child_process')
const { logger } = require('./../../../shared/logger')
const chomp = require('./../../../lib/helpers/chomp')
function scan () {
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
try {
// redirect stderr to stdout to capture and ignore it
childProcess.execSync('gitleaks version', { stdio: ['ignore', 'pipe', 'ignore'] })
} catch (error) {
logger.error('gitleaks: command not found')
logger.help('fix: install gitleaks: [brew install gitleaks]')
logger.help('fix: other install options: [https://github.com/gitleaks/gitleaks]')
process.exit(1)
return
}
let output = ''
try {
output = childProcess.execSync('gitleaks detect --no-banner --verbose 2>&1').toString() // gitleaks sends exit code 1 but puts data on stdout for failures, so we catch later and resurface the stdout
logger.info(chomp(output))
} catch (error) {
if (error.stdout) {
logger.error(chomp(error.stdout.toString()))
}
process.exit(1)
}
}
module.exports = scan

81
node_modules/@dotenvx/dotenvx/src/cli/actions/get.js generated vendored Normal file
View File

@@ -0,0 +1,81 @@
const { logger } = require('./../../shared/logger')
const conventions = require('./../../lib/helpers/conventions')
const escape = require('./../../lib/helpers/escape')
const catchAndLog = require('./../../lib/helpers/catchAndLog')
const Get = require('./../../lib/services/get')
function get (key) {
if (key) {
logger.debug(`key: ${key}`)
}
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const prettyPrint = options.prettyPrint || options.pp
const ignore = options.ignore || []
let envs = []
// handle shorthand conventions - like --convention=nextjs
if (options.convention) {
envs = conventions(options.convention).concat(this.envs)
} else {
envs = this.envs
}
try {
const opsOn = options.opsOff !== true
const { parsed, errors } = new Get(key, envs, options.overload, options.all, options.envKeysFile, opsOn).run()
for (const error of errors || []) {
if (options.strict) throw error // throw immediately if strict
if (ignore.includes(error.code)) {
continue // ignore error
}
logger.error(error.messageWithHelp)
}
if (key) {
const single = parsed[key]
if (single === undefined) {
console.log('')
} else {
console.log(single)
}
} else {
if (options.format === 'eval') {
let inline = ''
for (const [key, value] of Object.entries(parsed)) {
inline += `${key}=${escape(value)}\n`
}
inline = inline.trim()
console.log(inline)
} else if (options.format === 'shell') {
let inline = ''
for (const [key, value] of Object.entries(parsed)) {
inline += `${key}=${value} `
}
inline = inline.trim()
console.log(inline)
} else {
let space = 0
if (prettyPrint) {
space = 2
}
console.log(JSON.stringify(parsed, null, space))
}
}
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
module.exports = get

View File

@@ -0,0 +1,45 @@
const { logger } = require('./../../shared/logger')
const main = require('./../../lib/main')
function keypair (key) {
if (key) {
logger.debug(`key: ${key}`)
}
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const prettyPrint = options.prettyPrint || options.pp
const results = main.keypair(options.envFile, key, options.envKeysFile, options.opsOff)
if (typeof results === 'object' && results !== null) {
// inline shell format - env $(dotenvx keypair --format=shell) your-command
if (options.format === 'shell') {
let inline = ''
for (const [key, value] of Object.entries(results)) {
inline += `${key}=${value || ''} `
}
inline = inline.trim()
console.log(inline)
// json format
} else {
let space = 0
if (prettyPrint) {
space = 2
}
console.log(JSON.stringify(results, null, space))
}
} else {
if (results === undefined) {
console.log('')
process.exit(1)
} else {
console.log(results)
}
}
}
module.exports = keypair

24
node_modules/@dotenvx/dotenvx/src/cli/actions/ls.js generated vendored Normal file
View File

@@ -0,0 +1,24 @@
const treeify = require('object-treeify')
const { logger } = require('./../../shared/logger')
const main = require('./../../lib/main')
const ArrayToTree = require('./../../lib/helpers/arrayToTree')
function ls (directory) {
// debug args
logger.debug(`directory: ${directory}`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const filepaths = main.ls(directory, options.envFile, options.excludeEnvFile)
logger.debug(`filepaths: ${JSON.stringify(filepaths)}`)
const tree = new ArrayToTree(filepaths).run()
logger.debug(`tree: ${JSON.stringify(tree)}`)
logger.info(treeify(tree))
}
module.exports = ls

View File

@@ -0,0 +1,74 @@
const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const Rotate = require('./../../lib/services/rotate')
const catchAndLog = require('../../lib/helpers/catchAndLog')
const localDisplayPath = require('../../lib/helpers/localDisplayPath')
function rotate () {
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const envs = this.envs
const opsOn = options.opsOff !== true
// stdout - should not have a try so that exit codes can surface to stdout
if (options.stdout) {
const {
processedEnvs
} = new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
console.log(processedEnv.envSrc)
if (processedEnv.privateKeyAdded) {
console.log('')
console.log(processedEnv.envKeysSrc)
}
}
process.exit(0) // exit early
} else {
try {
const {
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = new Rotate(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
logger.verbose(`rotating ${processedEnv.envFilepath} (${processedEnv.filepath})`)
if (processedEnv.error) {
logger.warn(processedEnv.error.messageWithHelp)
} else if (processedEnv.changed) {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
if (processedEnv.privateKeyAdded) {
fsx.writeFileX(processedEnv.envKeysFilepath, processedEnv.envKeysSrc)
}
logger.verbose(`rotated ${processedEnv.envFilepath} (${processedEnv.filepath})`)
} else {
logger.verbose(`no changes ${processedEnv.envFilepath} (${processedEnv.filepath})`)
}
}
if (changedFilepaths.length > 0) {
const keyAddedEnv = processedEnvs.find((processedEnv) => processedEnv.privateKeyAdded)
let msg = `⟳ rotated (${changedFilepaths.join(',')})`
if (keyAddedEnv) {
const envKeysFilepath = localDisplayPath(keyAddedEnv.envKeysFilepath)
msg += ` + key (${envKeysFilepath})`
}
logger.success(msg)
} else if (unchangedFilepaths.length > 0) {
logger.info(`○ no changes (${unchangedFilepaths})`)
} else {
// do nothing - scenario when no .env files found
}
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
}
module.exports = rotate

109
node_modules/@dotenvx/dotenvx/src/cli/actions/run.js generated vendored Normal file
View File

@@ -0,0 +1,109 @@
const path = require('path')
const { logger } = require('./../../shared/logger')
const executeCommand = require('./../../lib/helpers/executeCommand')
const Run = require('./../../lib/services/run')
const catchAndLog = require('./../../lib/helpers/catchAndLog')
const conventions = require('./../../lib/helpers/conventions')
async function run () {
const commandArgs = this.args
logger.debug(`process command [${commandArgs.join(' ')}]`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
const ignore = options.ignore || []
// dotenvx-ops related
const opsOn = options.opsOff !== true
if (commandArgs.length < 1) {
const hasSeparator = process.argv.indexOf('--') !== -1
if (hasSeparator) {
logger.error('missing command after [dotenvx run --]. try [dotenvx run -- yourcommand]')
} else {
const realExample = options.envFile[0] || '.env'
logger.error(`ambiguous command due to missing '--' separator. try [dotenvx run -f ${realExample} -- yourcommand]`)
}
process.exit(1)
}
try {
let envs = []
// handle shorthand conventions - like --convention=nextjs
if (options.convention) {
envs = conventions(options.convention).concat(this.envs)
} else {
envs = this.envs
}
const {
processedEnvs,
readableStrings,
readableFilepaths,
uniqueInjectedKeys
} = new Run(envs, options.overload, process.env, options.envKeysFile, opsOn).run()
for (const processedEnv of processedEnvs) {
if (processedEnv.type === 'envFile') {
logger.verbose(`loading env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
}
if (processedEnv.type === 'env') {
logger.verbose(`loading env from string (${processedEnv.string})`)
}
for (const error of processedEnv.errors || []) {
if (ignore.includes(error.code)) {
logger.verbose(`ignored: ${error.message}`)
continue // ignore error
}
if (options.strict) throw error // throw if strict and not ignored
if (error.code === 'MISSING_ENV_FILE' && options.convention) { // do not output error for conventions (too noisy)
// intentionally quiet
} else {
logger.error(error.messageWithHelp)
}
}
// debug parsed
logger.debug(processedEnv.parsed)
// verbose/debug injected key/value
for (const [key, value] of Object.entries(processedEnv.injected || {})) {
logger.verbose(`${key} set`)
logger.debug(`${key} set to ${value}`)
}
// verbose/debug preExisted key/value
for (const [key, value] of Object.entries(processedEnv.preExisted || {})) {
logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
}
}
let msg = `injecting env (${uniqueInjectedKeys.length})`
if (readableFilepaths.length > 0 && readableStrings.length > 0) {
msg += ` from ${readableFilepaths.join(', ')}, and --env flag${readableStrings.length > 1 ? 's' : ''}`
} else if (readableFilepaths.length > 0) {
msg += ` from ${readableFilepaths.join(', ')}`
} else if (readableStrings.length > 0) {
msg += ` from --env flag${readableStrings.length > 1 ? 's' : ''}`
}
logger.successv(msg)
} catch (error) {
catchAndLog(error)
process.exit(1)
}
await executeCommand(commandArgs, process.env)
}
module.exports = run

77
node_modules/@dotenvx/dotenvx/src/cli/actions/set.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
const fsx = require('./../../lib/helpers/fsx')
const { logger } = require('./../../shared/logger')
const Sets = require('./../../lib/services/sets')
const catchAndLog = require('../../lib/helpers/catchAndLog')
const localDisplayPath = require('../../lib/helpers/localDisplayPath')
function set (key, value) {
logger.debug(`key: ${key}`)
logger.debug(`value: ${value}`)
const options = this.opts()
logger.debug(`options: ${JSON.stringify(options)}`)
// encrypt
let encrypt = true
if (options.plain) {
encrypt = false
}
try {
const envs = this.envs
const envKeysFilepath = options.envKeysFile
const opsOn = options.opsOff !== true
const {
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn).run()
let withEncryption = ''
if (encrypt) {
withEncryption = ' with encryption'
}
for (const processedEnv of processedEnvs) {
logger.verbose(`setting for ${processedEnv.envFilepath}`)
if (processedEnv.error) {
logger.warn(processedEnv.error.messageWithHelp)
} else {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`${processedEnv.key} set${withEncryption} (${processedEnv.envFilepath})`)
logger.debug(`${processedEnv.key} set${withEncryption} to ${processedEnv.value} (${processedEnv.envFilepath})`)
}
}
const keyAddedEnv = processedEnvs.find((processedEnv) => processedEnv.privateKeyAdded)
const keyAddedSuffix = keyAddedEnv ? ` + key (${localDisplayPath(keyAddedEnv.envKeysFilepath)})` : ''
if (changedFilepaths.length > 0) {
if (encrypt) {
logger.success(`◈ encrypted ${key} (${changedFilepaths.join(',')})${keyAddedSuffix}`)
} else {
logger.success(`◇ set ${key} (${changedFilepaths.join(',')})`)
}
} else if (encrypt && keyAddedEnv) {
const keyAddedEnvFilepath = keyAddedEnv.envFilepath || changedFilepaths[0] || '.env'
logger.success(`◈ encrypted ${key} (${keyAddedEnvFilepath})${keyAddedSuffix}`)
} else if (unchangedFilepaths.length > 0) {
logger.info(`○ no changes (${unchangedFilepaths})`)
} else {
// do nothing
}
// intentionally quiet: success line communicates key creation
} catch (error) {
catchAndLog(error)
process.exit(1)
}
}
module.exports = set

73
node_modules/@dotenvx/dotenvx/src/cli/commands/ext.js generated vendored Normal file
View File

@@ -0,0 +1,73 @@
const { Command } = require('commander')
const examples = require('./../examples')
const executeExtension = require('../../lib/helpers/executeExtension')
const removeDynamicHelpSection = require('../../lib/helpers/removeDynamicHelpSection')
const ext = new Command('ext')
ext
.description('🔌 extensions')
.allowUnknownOption()
ext
.argument('[command]', 'dynamic ext command')
.argument('[args...]', 'dynamic ext command arguments')
.action((command, args, cmdObj) => {
const rawArgs = process.argv.slice(3) // adjust the index based on where actual args start
executeExtension(ext, command, rawArgs)
})
// dotenvx ext ls
ext.command('ls')
.description('print all .env files in a tree structure')
.argument('[directory]', 'directory to list .env files from', '.')
.option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
.option('-ef, --exclude-env-file <excludeFilenames...>', 'path(s) to exclude from your env file(s) (default: none)')
.action(require('./../actions/ls'))
// dotenvx ext genexample
ext.command('genexample')
.description('generate .env.example')
.argument('[directory]', 'directory to generate from', '.')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
.action(require('./../actions/ext/genexample'))
// dotenvx ext gitignore
ext.command('gitignore')
.description('append to .gitignore file (and if existing, .dockerignore, .npmignore, and .vercelignore)')
.addHelpText('after', examples.gitignore)
.option('--pattern <patterns...>', 'pattern(s) to gitignore', ['.env*'])
.action(require('./../actions/ext/gitignore'))
// dotenvx ext prebuild
ext.command('prebuild')
.description('prevent including .env files in docker builds')
.addHelpText('after', examples.prebuild)
.argument('[directory]', 'directory to prevent including .env files from', '.')
.action(require('./../actions/ext/prebuild'))
// dotenvx ext precommit
ext.command('precommit')
.description('prevent committing .env files to code')
.addHelpText('after', examples.precommit)
.argument('[directory]', 'directory to prevent committing .env files from', '.')
.option('-i, --install', 'install to .git/hooks/pre-commit')
.action(require('./../actions/ext/precommit'))
// dotenvx scan
ext.command('scan')
.description('scan for leaked secrets')
.action(require('./../actions/ext/scan'))
// override helpInformation to hide dynamic commands
ext.helpInformation = function () {
const originalHelp = Command.prototype.helpInformation.call(this)
const lines = originalHelp.split('\n')
removeDynamicHelpSection(lines)
return lines.join('\n')
}
module.exports = ext

262
node_modules/@dotenvx/dotenvx/src/cli/dotenvx.js generated vendored Executable file
View File

@@ -0,0 +1,262 @@
#!/usr/bin/env node
/* c8 ignore start */
const { Command } = require('commander')
const program = new Command()
const { setLogLevel, logger } = require('../shared/logger')
const examples = require('./examples')
const packageJson = require('./../lib/helpers/packageJson')
const Errors = require('./../lib/helpers/errors')
const getCommanderVersion = require('./../lib/helpers/getCommanderVersion')
const executeDynamic = require('./../lib/helpers/executeDynamic')
const removeDynamicHelpSection = require('./../lib/helpers/removeDynamicHelpSection')
const removeOptionsHelpParts = require('./../lib/helpers/removeOptionsHelpParts')
const Session = require('./../db/session')
const sesh = new Session()
// for use with run
const envs = []
function collectEnvs (type) {
return function (value, previous) {
envs.push({ type, value })
return previous.concat([value])
}
}
// surface hoisting problems
const commanderVersion = getCommanderVersion()
if (commanderVersion && parseInt(commanderVersion.split('.')[0], 10) >= 12) {
const message = `dotenvx depends on commander@11.x.x but you are attempting to hoist commander@${commanderVersion}`
const error = new Errors({ message }).dangerousDependencyHoist()
logger.error(error.messageWithHelp)
}
// global log levels
program
.usage('run -- yourcommand')
.option('-l, --log-level <level>', 'set log level', 'info')
.option('-q, --quiet', 'sets log level to error')
.option('-v, --verbose', 'sets log level to verbose')
.option('-d, --debug', 'sets log level to debug')
.hook('preAction', (thisCommand, actionCommand) => {
const options = thisCommand.opts()
setLogLevel(options)
})
// for dynamic loading of dotenvx-ops, etc
program
.argument('[command]', 'dynamic command')
.argument('[args...]', 'dynamic command arguments')
.action((command, args, cmdObj) => {
const rawArgs = process.argv.slice(3) // adjust the index based on where actual args start
executeDynamic(program, command, rawArgs)
})
// cli
program
.name('dotenvx')
.description(packageJson.description)
.version(packageJson.version)
.allowUnknownOption()
// dotenvx run -- node index.js
const runAction = require('./actions/run')
program.command('run')
.description('inject env at runtime [dotenvx run -- yourcommand]')
.addHelpText('after', examples.run)
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
.option('--strict', 'process.exit(1) on any errors', false)
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.action(function (...args) {
this.envs = envs
runAction.apply(this, args)
})
// dotenvx get
const getAction = require('./actions/get')
program.command('get')
.usage('[KEY] [options]')
.description('return a single environment variable')
.argument('[KEY]', 'environment variable name')
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
.option('--strict', 'process.exit(1) on any errors', false)
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
.option('-a, --all', 'include all machine envs as well')
.option('-pp, --pretty-print', 'pretty print output')
.option('--pp', 'pretty print output (alias)')
.option('--format <type>', 'format of the output (json, shell, eval)', 'json')
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.action(function (...args) {
this.envs = envs
getAction.apply(this, args)
})
// dotenvx set
const setAction = require('./actions/set')
program.command('set')
.usage('<KEY> <value> [options]')
.description('encrypt a single environment variable')
.addHelpText('after', examples.set)
.allowUnknownOption()
.argument('KEY', 'KEY')
.argument('value', 'value')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.option('-c, --encrypt', 'encrypt value', true)
.option('-p, --plain', 'store value as plain text', false)
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.action(function (...args) {
this.envs = envs
setAction.apply(this, args)
})
// dotenvx encrypt
const encryptAction = require('./actions/encrypt')
program.command('encrypt')
.description('convert .env file(s) to encrypted .env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.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('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.option('--stdout', 'send to stdout')
.action(function (...args) {
this.envs = envs
encryptAction.apply(this, args)
})
// dotenvx decrypt
const decryptAction = require('./actions/decrypt')
program.command('decrypt')
.description('convert encrypted .env file(s) to plain .env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.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('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.option('--stdout', 'send to stdout')
.action(function (...args) {
this.envs = envs
decryptAction.apply(this, args)
})
// dotenvx rotate
const rotateAction = require('./actions/rotate')
program.command('rotate')
.description('rotate keypair(s) and re-encrypt .env file(s)')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.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('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.option('--stdout', 'send to stdout')
.action(function (...args) {
this.envs = envs
rotateAction.apply(this, args)
})
// dotenvx keypair
const keypairAction = require('./actions/keypair')
program.command('keypair')
.usage('[KEY] [options]')
.description('print public/private keys for .env file(s)')
.argument('[KEY]', 'environment variable key name')
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
.option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
.option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
.option('-pp, --pretty-print', 'pretty print output')
.option('--pp', 'pretty print output (alias)')
.option('--format <type>', 'format of the output (json, shell)', 'json')
.action(keypairAction)
// dotenvx ls
const lsAction = require('./actions/ls')
program.command('ls')
.description('print all .env files in a tree structure')
.argument('[directory]', 'directory to list .env files from', '.')
.option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
.option('-ef, --exclude-env-file <excludeFilenames...>', 'path(s) to exclude from your env file(s) (default: none)')
.action(lsAction)
// dotenvx help
program.command('help [command]')
.description('display help for command')
.action((command) => {
if (command) {
const subCommand = program.commands.find(c => c.name() === command)
if (subCommand) {
subCommand.outputHelp()
} else {
program.outputHelp()
}
} else {
program.outputHelp()
}
})
// dotenvx pro
program.addHelpText('after', ' ')
program.addHelpText('after', 'Advanced: ')
program.addHelpText('after', ' ops 🛡️ ops')
program.addHelpText('after', ' ext 🔌 extensions')
// dotenvx ext
program.addCommand(require('./commands/ext'))
//
// MOVED
//
const prebuildAction = require('./actions/ext/prebuild')
program.command('prebuild')
.description('DEPRECATED: moved to [dotenvx ext prebuild]')
.addHelpText('after', examples.prebuild)
.action(function (...args) {
logger.warn('DEPRECATION NOTICE: [prebuild] has moved to [dotenvx ext prebuild]')
prebuildAction.apply(this, args)
})
const precommitAction = require('./actions/ext/precommit')
program.command('precommit')
.description('DEPRECATED: moved to [dotenvx ext precommit]')
.addHelpText('after', examples.precommit)
.option('-i, --install', 'install to .git/hooks/pre-commit')
.action(function (...args) {
logger.warn('DEPRECATION NOTICE: [precommit] has moved to [dotenvx ext precommit]')
precommitAction.apply(this, args)
})
// override helpInformation to hide DEPRECATED and 'ext' commands
program.helpInformation = function () {
const originalHelp = Command.prototype.helpInformation.call(this)
const lines = originalHelp.split('\n')
removeDynamicHelpSection(lines)
removeOptionsHelpParts(lines)
// Filter out the hidden command from the help output
const filteredLines = lines.filter(line =>
!line.includes('DEPRECATED') &&
!line.includes('help [command]') &&
!line.includes('🔌 extensions') &&
!/^\s*ls\b/.test(line)
)
return filteredLines.join('\n')
}
/* c8 ignore stop */
program.parse(process.argv)

99
node_modules/@dotenvx/dotenvx/src/cli/examples.js generated vendored Normal file
View File

@@ -0,0 +1,99 @@
const run = function () {
return `
Examples:
\`\`\`
$ dotenvx run -- npm run dev
$ dotenvx run -- flask --app index run
$ dotenvx run -- php artisan serve
$ dotenvx run -- bin/rails s
\`\`\`
Try it:
\`\`\`
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env -- node index.js
[dotenvx] injecting env (1) from .env
Hello World
\`\`\`
`
}
const precommit = function () {
return `
Examples:
\`\`\`
$ dotenvx ext precommit
$ dotenvx ext precommit --install
\`\`\`
Try it:
\`\`\`
$ dotenvx ext precommit
[dotenvx@0.45.0][precommit] success
\`\`\`
`
}
const prebuild = function () {
return `
Examples:
\`\`\`
$ dotenvx ext prebuild
\`\`\`
Try it:
\`\`\`
$ dotenvx ext prebuild
[dotenvx@0.10.0][prebuild] success
\`\`\`
`
}
const gitignore = function () {
return `
Examples:
\`\`\`
$ dotenvx ext gitignore
$ dotenvx ext gitignore --pattern .env.keys
\`\`\`
Try it:
\`\`\`
$ dotenvx ext gitignore
▣ ignored .env* (.gitignore)
\`\`\`
`
}
const set = function () {
return `
Examples:
\`\`\`
$ dotenvx set KEY value
$ dotenvx set KEY "value with spaces"
$ dotenvx set KEY -- "---value with a dash---"
$ dotenvx set KEY -- "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
-----END OPENSSH PRIVATE KEY-----"
\`\`\`
`
}
module.exports = {
run,
precommit,
prebuild,
gitignore,
set
}

21
node_modules/@dotenvx/dotenvx/src/db/session.js generated vendored Normal file
View File

@@ -0,0 +1,21 @@
const Ops = require('./../lib/extensions/ops')
class Session {
constructor () {
this.ops = new Ops()
this.opsStatus = this.ops.status()
}
//
// opsOff/On
//
opsOn () {
return this.opsStatus === 'on'
}
opsOff () {
return !this.opsOn()
}
}
module.exports = Session

1
node_modules/@dotenvx/dotenvx/src/lib/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

1
node_modules/@dotenvx/dotenvx/src/lib/config.js generated vendored Normal file
View File

@@ -0,0 +1 @@
require('./main.js').config()

View File

@@ -0,0 +1,98 @@
const path = require('path')
const childProcess = require('child_process')
// const { logger } = require('./../../shared/logger')
class Ops {
constructor () {
this.opsLib = null
if (this._isForcedOff()) {
return
}
// check npm lib
try { this.opsLib = this._opsNpm() } catch (_e) {}
// check binary cli
if (!this.opsLib) {
try { this.opsLib = this._opsCli() } catch (_e) {}
}
if (this.opsLib) {
// logger.successv(`🛡️ ops: ${this.opsLib.status()}`)
}
}
status () {
if (this._isForcedOff() || !this.opsLib) {
return 'off'
}
return this.opsLib.status()
}
keypair (publicKey) {
if (this._isForcedOff() || !this.opsLib) {
return {}
}
return this.opsLib.keypair(publicKey)
}
observe (payload) {
if (!this._isForcedOff() && this.opsLib && this.opsLib.status() !== 'off') {
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64')
this.opsLib.observe(encoded)
}
}
//
// private
//
_opsNpm () {
const npmBin = path.resolve(process.cwd(), 'node_modules/.bin/dotenvx-ops')
return this._opsLib(npmBin)
}
_opsCli () {
return this._opsLib('dotenvx-ops')
}
_opsLib (binary) {
childProcess.execFileSync(binary, ['--version'], { stdio: ['pipe', 'pipe', 'ignore'] })
return {
status: () => {
return childProcess.execFileSync(binary, ['status'], { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
},
keypair: (publicKey) => {
const args = ['keypair']
if (publicKey) {
args.push(publicKey)
}
const output = childProcess.execFileSync(binary, args, { stdio: ['pipe', 'pipe', 'ignore'] }).toString().trim()
const parsed = JSON.parse(output.toString())
return parsed
},
observe: (encoded) => {
try {
const subprocess = childProcess.spawn(binary, ['observe', encoded], {
stdio: 'ignore',
detached: true
})
subprocess.unref() // let it run independently
} catch (e) {
// noop
}
}
}
}
_isForcedOff () {
return process.env.DOTENVX_OPS_OFF === 'true'
}
}
module.exports = Ops

View File

@@ -0,0 +1,61 @@
const quotes = require('./quotes')
const dotenvParse = require('./dotenvParse')
const escapeForRegex = require('./escapeForRegex')
const escapeDollarSigns = require('./escapeDollarSigns')
function append (src, key, appendValue) {
let output
let newPart = ''
const parsed = dotenvParse(src, true, true) // skip expanding \n and skip converting \r\n
const _quotes = quotes(src)
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
const quote = _quotes[key]
const originalValue = parsed[key]
newPart += `${key}=${quote}${originalValue},${appendValue}${quote}`
const escapedOriginalValue = escapeForRegex(originalValue)
// conditionally enforce end of line
let enforceEndOfLine = ''
if (escapedOriginalValue === '') {
enforceEndOfLine = '$' // EMPTY scenario
}
const currentPart = new RegExp(
'^' + // start of line
'(\\s*)?' + // spaces
'(export\\s+)?' + // export
key + // KEY
'\\s*=\\s*' + // spaces (KEY = value)
'["\'`]?' + // open quote
escapedOriginalValue + // escaped value
'["\'`]?' + // close quote
enforceEndOfLine
,
'gm' // (g)lobal (m)ultiline
)
const saferInput = escapeDollarSigns(newPart) // cleanse user inputted capture groups ($1, $2 etc)
// $1 preserves spaces
// $2 preserves export
output = src.replace(currentPart, `$1$2${saferInput}`)
} else {
newPart += `${key}="${appendValue}"`
// append
if (src.endsWith('\n')) {
newPart = newPart + '\n'
} else {
newPart = '\n' + newPart
}
output = src + newPart
}
return output
}
module.exports = append

View File

@@ -0,0 +1,27 @@
const path = require('path')
class ArrayToTree {
constructor (arr) {
this.arr = arr
}
run () {
const tree = {}
for (let i = 0; i < this.arr.length; i++) {
const normalizedPath = path.normalize(this.arr[i]) // normalize any strange paths
const parts = normalizedPath.split(path.sep) // use the platform-specific path segment separator
let current = tree
for (let j = 0; j < parts.length; j++) {
const part = parts[j]
current[part] = current[part] || {}
current = current[part]
}
}
return tree
}
}
module.exports = ArrayToTree

View File

@@ -0,0 +1,20 @@
const conventions = require('./conventions')
const dotenvOptionPaths = require('./dotenvOptionPaths')
function buildEnvs (options) {
// build envs using user set option.path
const optionPaths = dotenvOptionPaths(options) // [ '.env' ]
let envs = []
if (options.convention) { // handle shorthand conventions
envs = conventions(options.convention).concat(envs)
}
for (const optionPath of optionPaths) {
envs.push({ type: 'envFile', value: optionPath })
}
return envs
}
module.exports = buildEnvs

View File

@@ -0,0 +1,13 @@
const { logger } = require('./../../shared/logger')
function catchAndLog (error) {
logger.error(error.messageWithHelp)
if (error.debug) {
logger.debug(error.debug)
}
if (error.code) {
logger.debug(`ERROR_CODE: ${error.code}`)
}
}
module.exports = catchAndLog

View File

@@ -0,0 +1,5 @@
function chomp (value) {
return value.replace(/[\r\n]+$/, '')
}
module.exports = chomp

View File

@@ -0,0 +1,17 @@
const { WriteStream } = require('tty')
const getColorDepth = () => {
try {
return WriteStream.prototype.getColorDepth()
} catch (error) {
const term = process.env.TERM
if (term && (term.includes('256color') || term.includes('xterm'))) {
return 8 // 256 colors
}
return 4
}
}
module.exports = { getColorDepth }

View File

@@ -0,0 +1,28 @@
const Errors = require('./errors')
function conventions (convention) {
const env = process.env.DOTENV_ENV || process.env.NODE_ENV || 'development'
if (convention === 'nextjs') {
const canonicalEnv = ['development', 'test', 'production'].includes(env) && env
return [
canonicalEnv && { type: 'envFile', value: `.env.${canonicalEnv}.local` },
canonicalEnv !== 'test' && { type: 'envFile', value: '.env.local' },
canonicalEnv && { type: 'envFile', value: `.env.${canonicalEnv}` },
{ type: 'envFile', value: '.env' }
].filter(Boolean)
} else if (convention === 'flow') {
return [
{ type: 'envFile', value: `.env.${env}.local` },
{ type: 'envFile', value: `.env.${env}` },
{ type: 'envFile', value: '.env.local' },
{ type: 'envFile', value: '.env' },
{ type: 'envFile', value: '.env.defaults' }
]
} else {
throw new Errors({ convention }).invalidConvention()
}
}
module.exports = conventions

View File

@@ -0,0 +1,50 @@
const { decrypt } = require('eciesjs')
const Errors = require('./../errors')
const PREFIX = 'encrypted:'
function decryptKeyValue (key, value, privateKeyName, privateKey) {
let decryptedValue
let decryptionError
if (!value.startsWith(PREFIX)) {
return value
}
privateKey = privateKey || ''
if (privateKey.length <= 0) {
decryptionError = new Errors({ key, privateKeyName, privateKey }).missingPrivateKey()
} else {
const privateKeys = privateKey.split(',')
for (const privKey of privateKeys) {
const secret = Buffer.from(privKey, 'hex')
const encoded = value.substring(PREFIX.length)
const ciphertext = Buffer.from(encoded, 'base64')
try {
decryptedValue = decrypt(secret, ciphertext).toString()
decryptionError = null // reset to null error (scenario for multiple private keys)
break
} catch (e) {
if (e.message === 'Invalid private key') {
decryptionError = new Errors({ key, privateKeyName, privateKey }).invalidPrivateKey()
} else if (e.message === 'Unsupported state or unable to authenticate data') {
decryptionError = new Errors({ key, privateKeyName, privateKey }).wrongPrivateKey()
} else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {
decryptionError = new Errors({ key, privateKeyName, privateKey }).malformedEncryptedData()
} else {
decryptionError = new Errors({ key, privateKeyName, privateKey, message: e.message }).decryptionFailed()
}
}
}
}
if (decryptionError) {
throw decryptionError
}
return decryptedValue
}
module.exports = decryptKeyValue

View File

@@ -0,0 +1,12 @@
const { encrypt } = require('eciesjs')
const PREFIX = 'encrypted:'
function encryptValue (value, publicKey) {
const ciphertext = encrypt(publicKey, Buffer.from(value))
const encoded = Buffer.from(ciphertext, 'hex').toString('base64') // base64 encode ciphertext
return `${PREFIX}${encoded}`
}
module.exports = encryptValue

View File

@@ -0,0 +1,14 @@
module.exports = {
opsKeypair: require('./opsKeypair'),
localKeypair: require('./localKeypair'),
encryptValue: require('./encryptValue'),
decryptKeyValue: require('./decryptKeyValue'),
isEncrypted: require('./isEncrypted'),
isPublicKey: require('./isPublicKey'),
provision: require('./provision'),
provisionWithPrivateKey: require('./provisionWithPrivateKey'),
mutateSrc: require('./mutateSrc'),
// other
isFullyEncrypted: require('./../isFullyEncrypted')
}

View File

@@ -0,0 +1,7 @@
const ENCRYPTION_PATTERN = /^encrypted:.+/
function isEncrypted (value) {
return ENCRYPTION_PATTERN.test(value)
}
module.exports = isEncrypted

View File

@@ -0,0 +1,7 @@
const PUBLIC_KEY_PATTERN = /^DOTENV_PUBLIC_KEY/
function isPublicKey (key) {
return PUBLIC_KEY_PATTERN.test(key)
}
module.exports = isPublicKey

View File

@@ -0,0 +1,21 @@
const { PrivateKey } = require('eciesjs')
function localKeypair (existingPrivateKey) {
let kp
if (existingPrivateKey) {
kp = new PrivateKey(Buffer.from(existingPrivateKey, 'hex'))
} else {
kp = new PrivateKey()
}
const publicKey = kp.publicKey.toHex()
const privateKey = kp.secret.toString('hex')
return {
publicKey,
privateKey
}
}
module.exports = localKeypair

View File

@@ -0,0 +1,38 @@
const FIRST_TIME_KEYS_SRC = [
'#/------------------!DOTENV_PRIVATE_KEYS!-------------------/',
'#/ private decryption keys. DO NOT commit to source control /',
'#/ [how it works](https://dotenvx.com/encryption) /',
// '#/ backup with: `dotenvx ops backup` /',
'#/----------------------------------------------------------/'
].join('\n')
const path = require('path')
const fsx = require('./../fsx')
function mutateKeysSrc ({ envFilepath, keysFilepath, privateKeyName, privateKeyValue }) {
const filename = path.basename(envFilepath)
const filepath = path.resolve(envFilepath)
let resolvedKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
if (keysFilepath) {
resolvedKeysFilepath = path.resolve(keysFilepath)
}
const appendPrivateKey = [`# ${filename}`, `${privateKeyName}=${privateKeyValue}`, ''].join('\n')
let keysSrc = ''
if (fsx.existsSync(resolvedKeysFilepath)) {
keysSrc = fsx.readFileX(resolvedKeysFilepath)
}
keysSrc = keysSrc.length > 1 ? keysSrc : `${FIRST_TIME_KEYS_SRC}\n`
keysSrc = `${keysSrc}\n${appendPrivateKey}`
fsx.writeFileX(resolvedKeysFilepath, keysSrc) // TODO: don't write if ops
const envKeysFilepath = keysFilepath || path.join(path.dirname(envFilepath), path.basename(resolvedKeysFilepath))
return {
keysSrc,
envKeysFilepath
}
}
module.exports = mutateKeysSrc

View File

@@ -0,0 +1,24 @@
const path = require('path')
const preserveShebang = require('./../preserveShebang')
const prependPublicKey = require('./../prependPublicKey')
function mutateSrc ({ envSrc, envFilepath, keysFilepath, publicKeyName, publicKeyValue }) {
const filename = path.basename(envFilepath)
const filepath = path.resolve(envFilepath)
let resolvedKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
if (keysFilepath) {
resolvedKeysFilepath = path.resolve(keysFilepath)
}
const relativeFilepath = path.relative(path.dirname(filepath), resolvedKeysFilepath)
const ps = preserveShebang(envSrc)
const prependedPublicKey = prependPublicKey(publicKeyName, publicKeyValue, filename, relativeFilepath)
envSrc = `${ps.firstLinePreserved}${prependedPublicKey}\n${ps.envSrc}`
return {
envSrc
}
}
module.exports = mutateSrc

View File

@@ -0,0 +1,14 @@
const Ops = require('../../extensions/ops')
function opsKeypair (existingPublicKey) {
const kp = new Ops().keypair(existingPublicKey)
const publicKey = kp.public_key
const privateKey = kp.private_key
return {
publicKey,
privateKey
}
}
module.exports = opsKeypair

View File

@@ -0,0 +1,47 @@
const mutateSrc = require('./mutateSrc')
const mutateKeysSrc = require('./mutateKeysSrc')
const opsKeypair = require('./opsKeypair')
const localKeypair = require('./localKeypair')
const { keyNames } = require('../keyResolution')
function provision ({ envSrc, envFilepath, keysFilepath, opsOn }) {
opsOn = opsOn === true
const { publicKeyName, privateKeyName } = keyNames(envFilepath)
let publicKey
let privateKey
let keysSrc
let envKeysFilepath
let privateKeyAdded = false
if (opsOn) {
const kp = opsKeypair()
publicKey = kp.publicKey
privateKey = kp.privateKey
} else {
const kp = localKeypair()
publicKey = kp.publicKey
privateKey = kp.privateKey
}
const mutated = mutateSrc({ envSrc, envFilepath, keysFilepath, publicKeyName, publicKeyValue: publicKey })
envSrc = mutated.envSrc
if (!opsOn) {
const mutated = mutateKeysSrc({ envFilepath, keysFilepath, privateKeyName, privateKeyValue: privateKey })
keysSrc = mutated.keysSrc
envKeysFilepath = mutated.envKeysFilepath
privateKeyAdded = true
}
return {
envSrc,
keysSrc,
publicKey,
privateKey,
privateKeyAdded,
envKeysFilepath
}
}
module.exports = provision

View File

@@ -0,0 +1,26 @@
const Errors = require('./../errors')
const mutateSrc = require('./mutateSrc')
const localKeypair = require('./localKeypair')
function provisionWithPrivateKey ({ envSrc, envFilepath, keysFilepath, privateKeyValue, publicKeyValue, publicKeyName }) {
const { publicKey, privateKey } = localKeypair(privateKeyValue) // opsOn doesn't matter here since privateKeyValue was already discovered prior (via ops and local) and passed as privateKeyValue
// if derivation doesn't match what's in the file (or preset in env)
if (publicKeyValue && publicKeyValue !== publicKey) {
throw new Errors({ publicKey, publicKeyExisting: publicKeyValue }).mispairedPrivateKey()
}
// scenario when encrypting a monorepo second .env file from a prior generated -fk .env.keys file
if (!publicKeyValue) {
const mutated = mutateSrc({ envSrc, envFilepath, keysFilepath, publicKeyName, publicKeyValue: publicKey })
envSrc = mutated.envSrc
}
return {
envSrc,
publicKey,
privateKey
}
}
module.exports = provisionWithPrivateKey

View File

@@ -0,0 +1,22 @@
const fs = require('fs')
function detectEncoding (filepath) {
const buffer = fs.readFileSync(filepath)
// check for UTF-16LE BOM (Byte Order Mark)
if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
return 'utf16le'
}
/* c8 ignore start */
// check for UTF-8 BOM
if (buffer.length >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
return 'utf8'
}
/* c8 ignore stop */
return 'utf8'
}
module.exports = detectEncoding

View File

@@ -0,0 +1,21 @@
const resolveHome = require('./resolveHome')
function dotenvOptionPaths (options) {
let optionPaths = []
if (options && options.path) {
if (!Array.isArray(options.path)) {
optionPaths = [resolveHome(options.path)]
} else {
optionPaths = [] // reset default
for (const filepath of options.path) {
optionPaths.push(resolveHome(filepath))
}
}
}
return optionPaths
}
module.exports = dotenvOptionPaths

View File

@@ -0,0 +1,55 @@
// historical dotenv.parse - https://github.com/motdotla/dotenv)
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
function dotenvParse (src, skipExpandForDoubleQuotes = false, skipConvertingWindowsNewlines = false, collectAllValues = false) {
const obj = {}
// Convert buffer to string
let lines = src.toString()
// Convert line breaks to same format
if (!skipConvertingWindowsNewlines) {
lines = lines.replace(/\r\n?/mg, '\n')
}
let match
while ((match = LINE.exec(lines)) != null) {
const key = match[1]
// Default undefined or null to empty string
let value = (match[2] || '')
// Remove whitespace
value = value.trim()
// Check if double quoted
const maybeQuote = value[0]
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
// Expand newlines if double quoted
if (maybeQuote === '"' && !skipExpandForDoubleQuotes) {
value = value.replace(/\\n/g, '\n') // newline
value = value.replace(/\\r/g, '\r') // carriage return
value = value.replace(/\\t/g, '\t') // tabs
}
if (collectAllValues) {
// handle scenario where user mistakenly includes plaintext duplicate in .env:
//
// # .env
// HELLO="World"
// HELLO="encrypted:1234"
obj[key] = obj[key] || []
obj[key].push(value)
} else {
// Add to object
obj[key] = value
}
}
return obj
}
module.exports = dotenvParse

View File

@@ -0,0 +1,7 @@
const PRIVATE_KEY_NAME_SCHEMA = 'DOTENV_PRIVATE_KEY'
function dotenvPrivateKeyNames (processEnv) {
return Object.keys(processEnv).filter(key => key.startsWith(PRIVATE_KEY_NAME_SCHEMA))
}
module.exports = dotenvPrivateKeyNames

View File

@@ -0,0 +1,46 @@
const dotenvPrivateKeyNames = require('./../dotenvPrivateKeyNames')
const guessPrivateKeyFilename = require('./../guessPrivateKeyFilename')
const TYPE_ENV_FILE = 'envFile'
const DEFAULT_ENVS = [{ type: TYPE_ENV_FILE, value: '.env' }]
function envsFromDotenvPrivateKey (privateKeyNames) {
const envs = []
for (const privateKeyName of privateKeyNames) {
const filename = guessPrivateKeyFilename(privateKeyName)
envs.push({ type: TYPE_ENV_FILE, value: filename })
}
return envs
}
function determine (envs = [], processEnv) {
const privateKeyNames = dotenvPrivateKeyNames(processEnv)
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 (privateKeyNames.length > 0) {
return envsFromDotenvPrivateKey(privateKeyNames)
}
return DEFAULT_ENVS // default to .env file expectation
} else {
let fileAlreadySpecified = false
for (const env of envs) {
if (env.type === TYPE_ENV_FILE) {
fileAlreadySpecified = true
}
}
// return early since envs array objects already contain 1 .env file
if (fileAlreadySpecified) {
return envs
}
// no .env file specified as a flag so default to .env
return [...DEFAULT_ENVS, ...envs]
}
}
module.exports = determine

View File

@@ -0,0 +1,27 @@
const path = require('path')
function environment (filepath) {
const filename = path.basename(filepath).toLowerCase()
const parts = filename.split('.')
const possibleEnvironmentList = [...parts.slice(2)]
if (possibleEnvironmentList.length === 0) {
// handle .env1 -> development1
const environment = filename.replace('.env', 'development')
return environment
}
if (possibleEnvironmentList.length === 1) {
return possibleEnvironmentList[0]
}
if (possibleEnvironmentList.length === 2) {
return possibleEnvironmentList.join('_')
}
return possibleEnvironmentList.slice(0, 2).join('_')
}
module.exports = environment

View File

@@ -0,0 +1,8 @@
module.exports = {
buildEnvs: require('./../buildEnvs'),
determine: require('./determine'),
findEnvFiles: require('./../findEnvFiles'),
dotenvOptionPaths: require('./../dotenvOptionPaths'),
environment: require('./environment'),
conventions: require('./../conventions')
}

280
node_modules/@dotenvx/dotenvx/src/lib/helpers/errors.js generated vendored Normal file
View File

@@ -0,0 +1,280 @@
const truncate = require('./truncate')
const ISSUE_BY_CODE = {
COMMAND_EXITED_WITH_CODE: 'https://github.com/dotenvx/dotenvx/issues/new',
COMMAND_SUBSTITUTION_FAILED: 'https://github.com/dotenvx/dotenvx/issues/532',
DECRYPTION_FAILED: 'https://github.com/dotenvx/dotenvx/issues/757',
DANGEROUS_DEPENDENCY_HOIST: 'https://github.com/dotenvx/dotenvx/issues/622',
INVALID_COLOR: 'must be 256 colors',
INVALID_CONVENTION: 'https://github.com/dotenvx/dotenvx/issues/761',
INVALID_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/465',
INVALID_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/756',
MALFORMED_ENCRYPTED_DATA: 'https://github.com/dotenvx/dotenvx/issues/467',
MISPAIRED_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/752',
MISSING_DIRECTORY: 'https://github.com/dotenvx/dotenvx/issues/758',
MISSING_ENV_FILE: 'https://github.com/dotenvx/dotenvx/issues/484',
MISSING_ENV_FILES: 'https://github.com/dotenvx/dotenvx/issues/760',
MISSING_KEY: 'https://github.com/dotenvx/dotenvx/issues/759',
MISSING_LOG_LEVEL: 'must be valid log level',
MISSING_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/464',
PRECOMMIT_HOOK_MODIFY_FAILED: 'try again or report error',
WRONG_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/466'
}
class Errors {
constructor (options = {}) {
this.filepath = options.filepath
this.envFilepath = options.envFilepath
this.key = options.key
this.privateKey = options.privateKey
this.privateKeyName = options.privateKeyName
this.publicKeyName = options.publicKeyName
this.publicKey = options.publicKey
this.publicKeyExisting = options.publicKeyExisting
this.command = options.command
this.message = options.message
this.code = options.code
this.help = options.help
this.debug = options.debug
this.convention = options.convention
this.directory = options.directory
this.exitCode = options.exitCode
this.level = options.level
this.color = options.color
this.error = options.error
}
custom () {
const e = new Error(this.message)
if (this.code) e.code = this.code
if (this.help) e.help = this.help
if (this.code && !e.help) e.help = `fix: [${ISSUE_BY_CODE[this.code]}]`
e.messageWithHelp = `${e.message}. ${e.help}`
if (this.debug) e.debug = this.debug
return e
}
commandExitedWithCode () {
const code = 'COMMAND_EXITED_WITH_CODE'
const message = `[${code}] Command exited with exit code ${this.exitCode}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
commandSubstitutionFailed () {
const code = 'COMMAND_SUBSTITUTION_FAILED'
const message = `[${code}] could not eval ${this.key} containing command '${this.command}': ${this.message}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
decryptionFailed () {
const code = 'DECRYPTION_FAILED'
const message = `[${code}] ${this.message}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
dangerousDependencyHoist () {
const code = 'DANGEROUS_DEPENDENCY_HOIST'
const message = `[${code}] your environment has hoisted an incompatible version of a dotenvx dependency: ${this.message}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
invalidColor () {
const code = 'INVALID_COLOR'
const message = `[${code}] Invalid color ${this.color}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
invalidConvention () {
const code = 'INVALID_CONVENTION'
const message = `[${code}] invalid convention (${this.convention})`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
invalidPrivateKey () {
const code = 'INVALID_PRIVATE_KEY'
const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
invalidPublicKey () {
const code = 'INVALID_PUBLIC_KEY'
const message = `[${code}] could not encrypt using public key '${this.publicKeyName}=${truncate(this.publicKey)}'`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
malformedEncryptedData () {
const code = 'MALFORMED_ENCRYPTED_DATA'
const message = `[${code}] could not decrypt ${this.key} because encrypted data appears malformed`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
mispairedPrivateKey () {
const code = 'MISPAIRED_PRIVATE_KEY'
const message = `[${code}] private key's derived public key (${truncate(this.publicKey)}) does not match the existing public key (${truncate(this.publicKeyExisting)})`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingDirectory () {
const code = 'MISSING_DIRECTORY'
const message = `[${code}] missing directory (${this.directory})`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingEnvFile () {
const code = 'MISSING_ENV_FILE'
const envFilepath = this.envFilepath || '.env'
const message = `[${code}] missing file (${envFilepath})`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingEnvFiles () {
const code = 'MISSING_ENV_FILES'
const message = `[${code}] no .env* files found`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingKey () {
const code = 'MISSING_KEY'
const message = `[${code}] missing key (${this.key})`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingLogLevel () {
const code = 'MISSING_LOG_LEVEL'
const message = `[${code}] missing log level '${this.level}'. implement in logger`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
missingPrivateKey () {
const code = 'MISSING_PRIVATE_KEY'
const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
precommitHookModifyFailed () {
const code = 'PRECOMMIT_HOOK_MODIFY_FAILED'
const message = `[${code}] failed to modify pre-commit hook: ${this.error.message}`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
wrongPrivateKey () {
const code = 'WRONG_PRIVATE_KEY'
const message = `[${code}] could not decrypt ${this.key} using private key '${this.privateKeyName}=${truncate(this.privateKey)}'`
const help = `fix: [${ISSUE_BY_CODE[code]}]`
const e = new Error(message)
e.code = code
e.help = help
e.messageWithHelp = `${message}. ${help}`
return e
}
}
Errors.ISSUE_BY_CODE = ISSUE_BY_CODE
module.exports = Errors

View File

@@ -0,0 +1,5 @@
function escape (value) {
return JSON.stringify(value)
}
module.exports = escape

View File

@@ -0,0 +1,5 @@
function escapeDollarSigns (str) {
return str.replace(/\$/g, '$$$$')
}
module.exports = escapeDollarSigns

View File

@@ -0,0 +1,5 @@
function escapeForRegex (str) {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d')
}
module.exports = escapeForRegex

View File

@@ -0,0 +1,23 @@
const { execSync } = require('child_process')
const chomp = require('./chomp')
const Errors = require('./errors')
function evalKeyValue (key, value, processEnv, runningParsed) {
// Match everything between the outermost $() using a regex with non-capturing groups
const matches = value.match(/\$\(([^)]+(?:\)[^(]*)*)\)/g) || []
return matches.reduce((newValue, match) => {
const command = match.slice(2, -1) // Extract command by removing $() wrapper
let result
try {
result = execSync(command, { env: { ...processEnv, ...runningParsed } }).toString() // execute command (including runningParsed)
} catch (e) {
throw new Errors({ key, command, message: e.message.trim() }).commandSubstitutionFailed()
}
result = chomp(result) // chomp it
return newValue.replace(match, result) // Replace match with result
}, value)
}
module.exports = evalKeyValue

View File

@@ -0,0 +1,12 @@
const execa = require('execa')
/* c8 ignore start */
const pkgArgs = process.pkg ? { PKG_EXECPATH: '' } : {}
/* c8 ignore stop */
const execute = {
execa (command, args, options) {
return execa(command, args, { ...options, env: { ...options.env, ...pkgArgs } })
}
}
module.exports = execute

View File

@@ -0,0 +1,117 @@
const path = require('path')
const which = require('which')
const execute = require('./../../lib/helpers/execute')
const { logger } = require('./../../shared/logger')
const Errors = require('./errors')
async function executeCommand (commandArgs, env) {
const signals = [
'SIGHUP', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2'
]
logger.debug(`executing process command [${commandArgs.join(' ')}]`)
let child
let signalSent
/* c8 ignore start */
const sigintHandler = () => {
logger.debug('received SIGINT')
logger.debug('checking command process')
logger.debug(child)
if (child) {
logger.debug('sending SIGINT to command process')
signalSent = 'SIGINT'
child.kill('SIGINT') // Send SIGINT to the command process
} else {
logger.debug('no command process to send SIGINT to')
}
}
const sigtermHandler = () => {
logger.debug('received SIGTERM')
logger.debug('checking command process')
logger.debug(child)
if (child) {
logger.debug('sending SIGTERM to command process')
signalSent = 'SIGTERM'
child.kill('SIGTERM') // Send SIGTERM to the command process
} else {
logger.debug('no command process to send SIGTERM to')
}
}
const handleOtherSignal = (signal) => {
logger.debug(`received ${signal}`)
child.kill(signal)
}
/* c8 ignore stop */
try {
// ensure the first command is expanded
try {
commandArgs[0] = path.resolve(which.sync(`${commandArgs[0]}`))
logger.debug(`expanding process command to [${commandArgs.join(' ')}]`)
} catch (e) {
logger.debug(`could not expand process command. using [${commandArgs.join(' ')}]`)
}
// expand any other commands that follow a --
let expandNext = false
for (let i = 0; i < commandArgs.length; i++) {
if (commandArgs[i] === '--') {
expandNext = true
} else if (expandNext) {
try {
commandArgs[i] = path.resolve(which.sync(`${commandArgs[i]}`))
logger.debug(`expanding process command to [${commandArgs.join(' ')}]`)
} catch (e) {
logger.debug(`could not expand process command. using [${commandArgs.join(' ')}]`)
}
expandNext = false
}
}
child = execute.execa(commandArgs[0], commandArgs.slice(1), {
stdio: 'inherit',
env: { ...process.env, ...env }
})
process.on('SIGINT', sigintHandler)
process.on('SIGTERM', sigtermHandler)
signals.forEach(signal => {
process.on(signal, () => handleOtherSignal(signal))
})
// Wait for the command process to finish
const { exitCode } = await child
if (exitCode !== 0) {
logger.debug(`received exitCode ${exitCode}`)
throw new Errors({ exitCode }).commandExitedWithCode()
}
} catch (error) {
// no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
if (!['SIGINT', 'SIGTERM'].includes(signalSent || error.signal)) {
if (error.code === 'ENOENT') {
logger.error(`Unknown command: ${error.command}`)
} else {
logger.error(error.message)
}
}
// Exit with the error code from the command process, or 1 if unavailable
process.exit(error.exitCode || 1)
} finally {
// Clean up: Remove the SIGINT handler
process.removeListener('SIGINT', sigintHandler)
// Clean up: Remove the SIGTERM handler
process.removeListener('SIGTERM', sigtermHandler)
}
}
module.exports = executeCommand

View File

@@ -0,0 +1,65 @@
const path = require('path')
const childProcess = require('child_process')
const { logger } = require('../../shared/logger')
function installCommandForOps () {
return 'npm i -g @dotenvx/dotenvx-ops'
}
function opsBanner (installCommand) {
const lines = [
'',
' ██████╗ ██████╗ ███████╗',
' ██╔═══██╗██╔══██╗██╔════╝',
' ██║ ██║██████╔╝███████╗',
' ██║ ██║██╔═══╝ ╚════██║',
' ╚██████╔╝██║ ███████║',
' ╚═════╝ ╚═╝ ╚══════╝',
'',
' KEYS OFF COMPUTER: Add hardened key protection with dotenvx-ops.',
` Install now: [${installCommand}]`,
' Learn more: [https://dotenvx.com/ops]'
]
const innerWidth = Math.max(67, ...lines.map((line) => line.length))
const top = ` ${'_'.repeat(innerWidth)}`
const middle = lines.map((line) => `|${line.padEnd(innerWidth)}|`).join('\n')
const bottom = `|${'_'.repeat(innerWidth)}|`
return `${top}\n${middle}\n${bottom}`
}
function executeDynamic (program, command, rawArgs) {
if (!command) {
program.outputHelp()
process.exit(1)
return
}
// construct the full command line manually including flags
const commandIndex = rawArgs.indexOf(command)
const forwardedArgs = rawArgs.slice(commandIndex + 1)
logger.debug(`command: ${command}`)
logger.debug(`args: ${JSON.stringify(forwardedArgs)}`)
const binPath = path.join(process.cwd(), 'node_modules', '.bin')
const newPath = `${binPath}:${process.env.PATH}`
const env = { ...process.env, PATH: newPath }
const result = childProcess.spawnSync(`dotenvx-${command}`, forwardedArgs, { stdio: 'inherit', env })
if (result.error) {
if (command === 'ops') {
const installCommand = installCommandForOps()
console.log(opsBanner(installCommand))
} else {
logger.info(`error: unknown command '${command}'`)
}
}
if (result.status !== 0) {
process.exit(result.status)
}
}
module.exports = executeDynamic

View File

@@ -0,0 +1,39 @@
const path = require('path')
const childProcess = require('child_process')
const { logger } = require('../../shared/logger')
function executeExtension (ext, command, rawArgs) {
if (!command) {
ext.outputHelp()
process.exit(0)
return
}
// construct the full command line manually including flags
const commandIndex = rawArgs.indexOf(command)
const forwardedArgs = rawArgs.slice(commandIndex + 1)
logger.debug(`command: ${command}`)
logger.debug(`args: ${JSON.stringify(forwardedArgs)}`)
const binPath = path.join(process.cwd(), 'node_modules', '.bin')
const newPath = `${binPath}:${process.env.PATH}`
const env = { ...process.env, PATH: newPath }
const result = childProcess.spawnSync(`dotenvx-ext-${command}`, forwardedArgs, { stdio: 'inherit', env })
if (result.error) {
// list known extension here for convenience to the user
if (['vault', 'hub'].includes(command)) {
logger.warn(`[INSTALLATION_NEEDED] install dotenvx-ext-${command} to use [dotenvx ext ${command}] commands`)
logger.help('? see installation instructions [https://github.com/dotenvx/dotenvx-ext-vault]')
} else {
logger.info(`error: unknown command '${command}'`)
}
}
if (result.status !== 0) {
process.exit(result.status)
}
}
module.exports = executeExtension

View File

@@ -0,0 +1,25 @@
const fsx = require('./fsx')
const Errors = require('./errors')
const RESERVED_ENV_FILES = ['.env.project', '.env.keys', '.env.me', '.env.x', '.env.example']
function findEnvFiles (directory) {
try {
const files = fsx.readdirSync(directory)
const envFiles = files.filter(file =>
file.startsWith('.env') &&
!file.endsWith('.previous') &&
!RESERVED_ENV_FILES.includes(file)
)
return envFiles
} catch (e) {
if (e.code === 'ENOENT') {
throw new Errors({ directory }).missingDirectory()
} else {
throw e
}
}
}
module.exports = findEnvFiles

30
node_modules/@dotenvx/dotenvx/src/lib/helpers/fsx.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
const fs = require('fs')
const ENCODING = 'utf8'
function readFileX (filepath, encoding = null) {
if (!encoding) {
encoding = ENCODING
}
return fs.readFileSync(filepath, encoding) // utf8 default so it returns a string
}
function writeFileX (filepath, str) {
return fs.writeFileSync(filepath, str, ENCODING) // utf8 always
}
const fsx = {
chmodSync: fs.chmodSync,
existsSync: fs.existsSync,
readdirSync: fs.readdirSync,
readFileSync: fs.readFileSync,
writeFileSync: fs.writeFileSync,
appendFileSync: fs.appendFileSync,
// fsx special commands
readFileX,
writeFileX
}
module.exports = fsx

View File

@@ -0,0 +1,10 @@
const fs = require('fs')
const path = require('path')
function getCommanderVersion () {
const commanderMain = require.resolve('commander')
const pkgPath = path.join(commanderMain, '..', 'package.json')
return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version
}
module.exports = getCommanderVersion

View File

@@ -0,0 +1,15 @@
const PREFIX = 'DOTENV_PRIVATE_KEY'
function guessPrivateKeyFilename (privateKeyName) {
// .env
if (privateKeyName === PREFIX) {
return '.env'
}
const filenameSuffix = privateKeyName.substring(`${PREFIX}_`.length).split('_').join('.').toLowerCase()
// .env.ENVIRONMENT
return `.env.${filenameSuffix}`
}
module.exports = guessPrivateKeyFilename

View File

@@ -0,0 +1,73 @@
const fsx = require('./fsx')
const path = require('path')
const Errors = require('./errors')
const HOOK_SCRIPT = `#!/bin/sh
if command -v dotenvx 2>&1 >/dev/null
then
dotenvx ext precommit
elif npx dotenvx -V >/dev/null 2>&1
then
npx dotenvx ext precommit
else
echo "[dotenvx][precommit] 'dotenvx' command not found"
echo "[dotenvx][precommit] ? install it with [curl -fsS https://dotenvx.sh | sh]"
echo "[dotenvx][precommit] ? other install options [https://dotenvx.com/docs/install]"
exit 1
fi
`
class InstallPrecommitHook {
constructor () {
this.hookPath = path.join('.git', 'hooks', 'pre-commit')
}
run () {
let successMessage
try {
// Check if the pre-commit file already exists
if (this._exists()) {
// Check if 'dotenvx precommit' already exists in the file
if (this._currentHook().includes('dotenvx ext precommit')) {
// do nothing
successMessage = `dotenvx ext precommit exists [${this.hookPath}]`
} else {
this._appendHook()
successMessage = `dotenvx ext precommit appended [${this.hookPath}]`
}
} else {
this._createHook()
successMessage = `dotenvx ext precommit installed [${this.hookPath}]`
}
return {
successMessage
}
} catch (err) {
throw new Errors({ error: err }).precommitHookModifyFailed()
}
}
_exists () {
return fsx.existsSync(this.hookPath)
}
_currentHook () {
return fsx.readFileX(this.hookPath)
}
_createHook () {
// If the pre-commit file doesn't exist, create a new one with the hookScript
fsx.writeFileX(this.hookPath, HOOK_SCRIPT)
fsx.chmodSync(this.hookPath, '755') // Make the file executable
}
_appendHook () {
// Append 'dotenvx precommit' to the existing file
fsx.appendFileSync(this.hookPath, '\n' + HOOK_SCRIPT)
}
}
module.exports = InstallPrecommitHook

View File

@@ -0,0 +1,27 @@
const dotenvParse = require('./dotenvParse')
const isEncrypted = require('./cryptography/isEncrypted')
const isPublicKey = require('./cryptography/isPublicKey')
function isFullyEncrypted (src) {
const parsed = dotenvParse(src, false, false, true) // collect all values
for (const [key, values] of Object.entries(parsed)) {
// handle scenario where user mistakenly includes plaintext duplicate in .env:
//
// # .env
// HELLO="World"
// HELLO="encrypted:1234"
//
// key => [value1, ...]
for (const value of values) {
const result = isEncrypted(value) || isPublicKey(key)
if (!result) {
return false
}
}
}
return true
}
module.exports = isFullyEncrypted

View File

@@ -0,0 +1,19 @@
const fsx = require('./fsx')
const ignore = require('ignore')
function isIgnoringDotenvKeys () {
if (!fsx.existsSync('.gitignore')) {
return false
}
const gitignore = fsx.readFileX('.gitignore')
const ig = ignore(gitignore).add(gitignore)
if (!ig.ignores('.env.keys')) {
return false
}
return true
}
module.exports = isIgnoringDotenvKeys

View File

@@ -0,0 +1,13 @@
module.exports = {
keyNames: require('./keyNames'),
keyValues: require('./keyValues'),
// private
// private keys are resolved via keyValues()
// other
readProcessKey: require('./readProcessKey'),
readFileKey: require('./readFileKey'),
guessPrivateKeyFilename: require('./../guessPrivateKeyFilename'),
dotenvPrivateKeyNames: require('./../dotenvPrivateKeyNames')
}

View File

@@ -0,0 +1,24 @@
const path = require('path')
const environment = require('./../envResolution/environment')
function keyNames (filepath) {
const filename = path.basename(filepath).toLowerCase()
// .env
if (filename === '.env') {
return {
publicKeyName: 'DOTENV_PUBLIC_KEY',
privateKeyName: 'DOTENV_PRIVATE_KEY'
}
}
// .env.ENVIRONMENT
const resolvedEnvironment = environment(filename).toUpperCase()
return {
publicKeyName: `DOTENV_PUBLIC_KEY_${resolvedEnvironment}`,
privateKeyName: `DOTENV_PRIVATE_KEY_${resolvedEnvironment}`
}
}
module.exports = keyNames

View File

@@ -0,0 +1,85 @@
const path = require('path')
const fsx = require('./../fsx')
const dotenvParse = require('./../dotenvParse')
const keyNames = require('./keyNames')
const readProcessKey = require('./readProcessKey')
const readFileKey = require('./readFileKey')
const opsKeypair = require('../cryptography/opsKeypair')
function invertForPrivateKeyName (filepath) {
const PUBLIC_KEY_SCHEMA = 'DOTENV_PUBLIC_KEY'
const PRIVATE_KEY_SCHEMA = 'DOTENV_PRIVATE_KEY'
if (!fsx.existsSync(filepath)) {
return null
}
const envSrc = fsx.readFileX(filepath)
const envParsed = dotenvParse(envSrc)
let publicKeyName
for (const keyName of Object.keys(envParsed)) {
if (keyName === PUBLIC_KEY_SCHEMA || keyName.startsWith(PUBLIC_KEY_SCHEMA)) {
publicKeyName = keyName // find DOTENV_PUBLIC_KEY* in filename
}
}
if (publicKeyName) {
return publicKeyName.replace(PUBLIC_KEY_SCHEMA, PRIVATE_KEY_SCHEMA) // return inverted (DOTENV_PUBLIC_KEY* -> DOTENV_PRIVATE_KEY*) if found
}
return null
}
function keyValues (filepath, opts = {}) {
let keysFilepath = opts.keysFilepath || null
const opsOn = opts.opsOn === true
const names = keyNames(filepath)
const publicKeyName = names.publicKeyName // DOTENV_PUBLIC_KEY_${ENVIRONMENT}
let privateKeyName = names.privateKeyName // DOTENV_PRIVATE_KEY_${ENVIRONMENT}
let publicKey = null
let privateKey = null
// public key: process.env first, then .env*
publicKey = readProcessKey(publicKeyName)
if (!publicKey) {
publicKey = readFileKey(publicKeyName, filepath) || null
}
// private key: process.env first, then .env.keys, then invert public key
privateKey = readProcessKey(privateKeyName)
if (!privateKey) {
if (keysFilepath) { // user specified -fk flag
keysFilepath = path.resolve(keysFilepath)
} else {
keysFilepath = path.resolve(path.dirname(filepath), '.env.keys') // typical scenario
}
privateKey = readFileKey(privateKeyName, keysFilepath)
}
// invert
if (!privateKey) {
privateKeyName = invertForPrivateKeyName(filepath)
if (privateKeyName) {
privateKey = readProcessKey(privateKeyName)
if (!privateKey) {
privateKey = readFileKey(privateKeyName, keysFilepath)
}
}
}
// ops
if (opsOn && !privateKey && publicKey && publicKey.length > 0) {
const kp = opsKeypair(publicKey)
privateKey = kp.privateKey
}
return {
publicKeyValue: publicKey || null, // important to make sure name is rendered
privateKeyValue: privateKey || null // importan to make sure name is rendered
}
}
module.exports = keyValues

View File

@@ -0,0 +1,15 @@
const fsx = require('./../fsx')
const dotenvParse = require('./../dotenvParse')
function readFileKey (keyName, filepath) {
if (fsx.existsSync(filepath)) {
const src = fsx.readFileX(filepath)
const parsed = dotenvParse(src)
if (parsed[keyName] && parsed[keyName].length > 0) {
return parsed[keyName]
}
}
}
module.exports = readFileKey

View File

@@ -0,0 +1,7 @@
function readProcessKey (keyName) {
if (process.env[keyName] && process.env[keyName].length > 0) {
return process.env[keyName]
}
}
module.exports = readProcessKey

View File

@@ -0,0 +1,11 @@
const path = require('path')
function localDisplayPath (filepath) {
if (!filepath) return '.env.keys'
if (!path.isAbsolute(filepath)) return filepath
const relative = path.relative(process.cwd(), filepath)
return relative || path.basename(filepath)
}
module.exports = localDisplayPath

View File

@@ -0,0 +1,3 @@
const { name, version, description } = require('../../../package.json')
module.exports = { name, version, description }

211
node_modules/@dotenvx/dotenvx/src/lib/helpers/parse.js generated vendored Normal file
View File

@@ -0,0 +1,211 @@
const decryptKeyValue = require('./cryptography/decryptKeyValue')
const evalKeyValue = require('./evalKeyValue')
const resolveEscapeSequences = require('./resolveEscapeSequences')
class Parse {
static LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
constructor (src, privateKey = null, processEnv = process.env, overload = false, privateKeyName = null) {
this.src = src
this.privateKey = privateKey
this.privateKeyName = privateKeyName
this.processEnv = processEnv
this.overload = overload
this.parsed = {}
this.preExisted = {}
this.injected = {}
this.errors = []
// for use with progressive expansion
this.runningParsed = {}
// for use with stopping expansion for literals
this.literals = {}
}
run () {
const lines = this.getLines()
let match
while ((match = Parse.LINE.exec(lines)) !== null) {
const key = match[1]
const value = match[2]
const quote = this.quote(value) // must be raw match
this.parsed[key] = this.clean(value, quote) // file value
if (!this.overload && this.inProcessEnv(key)) {
this.parsed[key] = this.processEnv[key] // use process.env pre-existing value
}
// decrypt
try {
this.parsed[key] = this.decrypt(key, this.parsed[key])
} catch (e) {
this.errors.push(e)
}
// eval empty, double, or backticks
let evaled = false
if (quote !== "'" && (!this.inProcessEnv(key) || this.processEnv[key] === this.parsed[key])) {
const priorEvaled = this.parsed[key]
// eval
try {
this.parsed[key] = this.eval(key, priorEvaled)
} catch (e) {
this.errors.push(e)
}
if (priorEvaled !== this.parsed[key]) {
evaled = true
}
}
// expand empty, double, or backticks
if (!evaled && quote !== "'" && (!this.processEnv[key] || this.overload)) {
this.parsed[key] = resolveEscapeSequences(this.expand(this.parsed[key]))
}
if (quote === "'") {
this.literals[key] = this.parsed[key]
}
// for use with progressive expansion
this.runningParsed[key] = this.parsed[key]
if (Object.prototype.hasOwnProperty.call(this.processEnv, key) && !this.overload) {
this.preExisted[key] = this.processEnv[key] // track preExisted
} else {
this.injected[key] = this.parsed[key] // track injected
}
}
return {
parsed: this.parsed,
processEnv: this.processEnv,
injected: this.injected,
preExisted: this.preExisted,
errors: this.errors
}
}
trimmer (value) {
// Default undefined or null to empty string
return (value || '').trim()
}
quote (value) {
const v = this.trimmer(value)
const maybeQuote = v[0]
let q = ''
switch (maybeQuote) {
// single
case "'":
q = "'"
break
// double
case '"':
q = '"'
break
// backtick
case '`':
q = '`'
break
// empty
default:
q = ''
}
return q
}
clean (value, _quote) {
let v = this.trimmer(value)
// Remove surrounding quotes
v = v.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
// Expand newlines if double quoted
if (_quote === '"') {
v = v.replace(/\\n/g, '\n') // newline
v = v.replace(/\\r/g, '\r') // carriage return
v = v.replace(/\\t/g, '\t') // tabs
}
return v
}
decrypt (key, value) {
return decryptKeyValue(key, value, this.privateKeyName, this.privateKey)
}
eval (key, value) {
return evalKeyValue(key, value, this.processEnv, this.runningParsed)
}
expand (value) {
let env = { ...this.runningParsed, ...this.processEnv } // typically process.env wins
if (this.overload) {
env = { ...this.processEnv, ...this.runningParsed } // parsed wins
}
const regex = /(?<!\\)\${([^{}]+)}|(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)/g
let result = value
let match
while ((match = regex.exec(result)) !== null) {
const [template, bracedExpression, unbracedExpression] = match
const expression = bracedExpression || unbracedExpression
// match the operators `:+`, `+`, `:-`, and `-`
const opRegex = /(:\+|\+|:-|-)/
// find first match
const opMatch = expression.match(opRegex)
const splitter = opMatch ? opMatch[0] : null
const r = expression.split(splitter)
let defaultValue
let value
const key = r.shift()
if ([':+', '+'].includes(splitter)) {
defaultValue = env[key] ? r.join(splitter) : ''
value = null
} else {
defaultValue = r.join(splitter)
value = env[key]
}
if (value) {
result = result.replace(template, value)
} else {
result = result.replace(template, defaultValue)
}
// if the result equaled what was in env then stop expanding - handle self-referential check as well
if (result === env[key]) {
break
}
// if the result came from what was a literal value then stop expanding
// BUT only if the literal value contains expansion patterns (${...} or $VAR)
if (this.literals[key] && /\$\{[^}]+\}|\$[A-Za-z_][A-Za-z0-9_]*/.test(this.literals[key])) {
break
}
regex.lastIndex = 0 // reset regex search position to re-evaluate after each replacement
}
return result
}
inProcessEnv (key) {
return Object.prototype.hasOwnProperty.call(this.processEnv, key)
}
getLines () {
return (this.src || '').toString().replace(/\r\n?/mg, '\n') // Convert buffer to string and Convert line breaks to same format
}
}
module.exports = Parse

View File

@@ -0,0 +1,10 @@
function pluralize (word, count) {
// simple pluralization: add 's' at the end
if (count === 0 || count > 1) {
return word + 's'
} else {
return word
}
}
module.exports = pluralize

View File

@@ -0,0 +1,17 @@
function prependPublicKey (publicKeyName, publicKey, filename, relativeFilepath = '.env.keys') {
const comment = relativeFilepath === '.env.keys'
? ''
: ` # -fk ${relativeFilepath}`
return [
'#/-------------------[DOTENV_PUBLIC_KEY]--------------------/',
'#/ public-key encryption for .env files /',
'#/ [how it works](https://dotenvx.com/encryption) /',
'#/----------------------------------------------------------/',
`${publicKeyName}="${publicKey}"${comment}`,
'',
`# ${filename}`
].join('\n')
}
module.exports = prependPublicKey

View File

@@ -0,0 +1,16 @@
function preserveShebang (envSrc) {
const [firstLine, ...remainingLines] = envSrc.split('\n')
let firstLinePreserved = ''
if (firstLine.startsWith('#!')) {
firstLinePreserved = firstLine + '\n'
envSrc = remainingLines.join('\n')
}
return {
firstLinePreserved,
envSrc
}
}
module.exports = preserveShebang

View File

@@ -0,0 +1,36 @@
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
function quotes (src) {
const obj = {}
// Convert buffer to string
let lines = src.toString()
// Convert line breaks to same format
lines = lines.replace(/\r\n?/mg, '\n')
let match
while ((match = LINE.exec(lines)) != null) {
const key = match[1]
// Default undefined or null to empty string
let value = (match[2] || '')
// Remove whitespace
value = value.trim()
// Check if double quoted
const maybeQuote = value[0]
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
if (maybeQuote === value[0]) {
obj[key] = ''
} else {
obj[key] = maybeQuote
}
}
return obj
}
module.exports = quotes

View File

@@ -0,0 +1,21 @@
// Remove Arguments section from help text. example:
// Arguments:
// command dynamic command
// args dynamic command arguments
function removeDynamicHelpSection (lines) {
let argumentsHelpIndex
for (let i = 0; i < lines.length; i++) {
if (lines[i] === 'Arguments:') {
argumentsHelpIndex = i
break
}
}
if (argumentsHelpIndex) {
lines.splice(argumentsHelpIndex, 4) // remove Arguments and the following 3 lines
}
return lines
}
module.exports = removeDynamicHelpSection

View File

@@ -0,0 +1,11 @@
// Remove [options] from help text. example:
function removeOptionsHelpParts (lines) {
for (let i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(' [options]', '')
}
return lines
}
module.exports = removeOptionsHelpParts

View File

@@ -0,0 +1,69 @@
const quotes = require('./quotes')
const dotenvParse = require('./dotenvParse')
const escapeForRegex = require('./escapeForRegex')
const escapeDollarSigns = require('./escapeDollarSigns')
function replace (src, key, replaceValue) {
let output
let newPart = ''
const parsed = dotenvParse(src, true, true) // skip expanding \n and skip converting \r\n
const _quotes = quotes(src)
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
const quote = _quotes[key]
newPart += `${key}=${quote}${replaceValue}${quote}`
const originalValue = parsed[key]
const escapedOriginalValue = escapeForRegex(originalValue)
// conditionally enforce end of line
let enforceEndOfLine = ''
if (escapedOriginalValue === '') {
enforceEndOfLine = '$' // EMPTY scenario
// if empty quote and consecutive newlines
const newlineMatch = src.match(new RegExp(`${key}\\s*=\\s*\n\n`, 'm')) // match any consecutive newline scenario for a blank value
if (quote === '' && newlineMatch) {
const newlineCount = (newlineMatch[0].match(/\n/g)).length - 1
for (let i = 0; i < newlineCount; i++) {
newPart += '\n' // re-append the extra newline to preserve user's format choice
}
}
}
const currentPart = new RegExp(
'^' + // start of line
'(\\s*)?' + // spaces
'(export\\s+)?' + // export
key + // KEY
'\\s*=\\s*' + // spaces (KEY = value)
'["\'`]?' + // open quote
escapedOriginalValue + // escaped value
'["\'`]?' + // close quote
enforceEndOfLine
,
'gm' // (g)lobal (m)ultiline
)
const saferInput = escapeDollarSigns(newPart) // cleanse user inputted capture groups ($1, $2 etc)
// $1 preserves spaces
// $2 preserves export
output = src.replace(currentPart, `$1$2${saferInput}`)
} else {
newPart += `${key}="${replaceValue}"`
// append
if (src.endsWith('\n')) {
newPart = newPart + '\n'
} else {
newPart = '\n' + newPart
}
output = src + newPart
}
return output
}
module.exports = replace

View File

@@ -0,0 +1,5 @@
function resolveEscapeSequences (value) {
return value.replace(/\\\$/g, '$')
}
module.exports = resolveEscapeSequences

View File

@@ -0,0 +1,12 @@
const os = require('os')
const path = require('path')
function resolveHome (filepath) {
if (filepath[0] === '~') {
return path.join(os.homedir(), filepath.slice(1))
}
return filepath
}
module.exports = resolveHome

View File

@@ -0,0 +1,5 @@
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
module.exports = sleep

View File

@@ -0,0 +1,10 @@
function truncate (str, showChar = 7) {
if (str && str.length > 0) {
const visiblePart = str.slice(0, showChar)
return visiblePart + '…'
} else {
return ''
}
}
module.exports = truncate

336
node_modules/@dotenvx/dotenvx/src/lib/main.d.ts generated vendored Normal file
View File

@@ -0,0 +1,336 @@
import type { URL } from 'url';
export interface DotenvParseOptions {
/**
* Override any environment variables that have already been set on your machine with values from your .env file.
* @default false
* @example require('@dotenvx/dotenvx').config({ overload: true })
* @alias overload
*/
overload?: boolean;
/**
* @default false
* @alias override
*/
override?: boolean;
/**
* Specify an object to read existing environment variables from. Defaults to process.env environment variables.
*
* @default process.env
* @example const processEnv = {}; require('@dotenvx/dotenvx').parse('HELLO=World', { processEnv: processEnv })
*/
processEnv?: DotenvPopulateInput;
/**
* Specify a privateKey to decrypt any encrypted contents with.
*
* @default undefined
* @example require('@dotenvx/dotenvx').parse('HELLO="encrypted:BE9Y7LKANx77X1pv1HnEoil93fPa5c9rpL/1ps48uaRT9zM8VR6mHx9yM+HktKdsPGIZELuZ7rr2mn1gScsmWitppAgE/1lVprNYBCqiYeaTcKXjDUXU5LfsEsflnAsDhT/kWG1l"', { privateKey: 'a4547dcd9d3429615a3649bb79e87edb62ee6a74b007075e9141ae44f5fb412c' })
*/
privateKey?: string;
}
export interface DotenvParseOutput {
[name: string]: string;
}
/**
* Parses a string or buffer in the .env file format into an object.
*
* @see https://dotenvx.com/docs
* @param src - contents to be parsed. example: `'DB_HOST=localhost'`
* @param options - additional options. example: `{ processEnv: {}, privateKey: '<privateKey>', overload: false }`
* @returns an object with keys and values based on `src`. example: `{ DB_HOST : 'localhost' }`
*/
export function parse<T extends DotenvParseOutput = DotenvParseOutput>(
src: string | Buffer,
options?: DotenvParseOptions
): T;
export interface DotenvConfigOptions {
/**
* Specify a custom path if your file containing environment variables is located elsewhere.
* Can also be an array of strings, specifying multiple paths.
*
* @default require('path').resolve(process.cwd(), '.env')
* @example require('@dotenvx/dotenvx').config({ path: '/custom/path/to/.env' })
* @example require('@dotenvx/dotenvx').config({ path: ['/path/to/first.env', '/path/to/second.env'] })
*/
path?: string | string[] | URL;
/**
* Specify the encoding of your file containing environment variables.
*
* @default 'utf8'
* @example require('@dotenvx/dotenvx').config({ encoding: 'latin1' })
*/
encoding?: string;
/**
* Override any environment variables that have already been set on your machine with values from your .env file.
* @default false
* @example require('@dotenvx/dotenvx').config({ overload: true })
* @alias overload
*/
overload?: boolean;
/**
* @default false
* @alias override
*/
override?: boolean;
/**
* Throw immediately if an error is encountered - like a missing .env file.
* @default false
* @example require('@dotenvx/dotenvx').config({ strict: true })
*/
strict?: boolean;
/**
* Suppress specific errors like MISSING_ENV_FILE. The error keys can be found
* in src/lib/helpers/errors.js
* @default []
* @example require('@dotenvx/dotenvx').config({ ignore: ['MISSING_ENV_FILE'] })
*/
ignore?: string[];
/**
* Specify an object to write your secrets to. Defaults to process.env environment variables.
*
* @default process.env
* @example const processEnv = {}; require('@dotenvx/dotenvx').config({ processEnv: processEnv })
*/
processEnv?: DotenvPopulateInput;
/**
* Customize the path to your .env.keys file. This is useful with monorepos.
* @default []
* @example require('@dotenvx/dotenvx').config({ envKeysFile: '../../.env.keys'} })
*/
envKeysFile?: string;
/**
* Legacy option retained for compatibility.
*
* @default undefined
* @example require('@dotenvx/dotenvx').config({ DOTENV_KEY: 'dotenv://:key_1234…' })
*/
DOTENV_KEY?: string;
/**
* Load a .env convention (available conventions: 'nextjs, flow')
*/
convention?: string;
/**
* Turn on logging to help debug why certain keys or values are not being set as you expect.
*
* @default false
* @example require('@dotenvx/dotenvx').config({ debug: process.env.DEBUG })
*/
debug?: boolean;
verbose?: boolean;
quiet?: boolean;
logLevel?:
| 'error'
| 'warn'
| 'success'
| 'successv'
| 'info'
| 'help'
| 'verbose'
| 'debug';
/**
* Turn off Dotenvx Ops features - https://dotenvx.com/ops
*
* @default false
* @example require('@dotenvx/dotenvx').config({ opsOff: true })
*/
opsOff?: boolean;
}
export interface DotenvConfigOutput {
error?: Error;
parsed?: DotenvParseOutput;
}
export interface DotenvPopulateInput {
[name: string]: string;
}
/**
* Loads `.env` file contents into process.env by default.
*
* @see https://dotenvx.com/docs
*
* @param options - additional options. example: `{ path: './custom/path', encoding: 'latin1', debug: true, overload: false }`
* @returns an object with a `parsed` key if successful or `error` key if an error occurred. example: { parsed: { KEY: 'value' } }
*
*/
export function config(options?: DotenvConfigOptions): DotenvConfigOutput;
export interface SetOptions {
/**
* Specify a custom path if your file containing environment variables is located elsewhere.
* Can also be an array of strings, specifying multiple paths.
*
* @default require('path').resolve(process.cwd(), '.env')
* @example require('@dotenvx/dotenvx').set(key, value, { path: '/custom/path/to/.env' })
* @example require('@dotenvx/dotenvx').set(key, value, { path: ['/path/to/first.env', '/path/to/second.env'] })
*/
path?: string | string[] | URL;
/**
* Customize the path to your .env.keys file. This is useful with monorepos.
* @default []
* @example require('@dotenvx/dotenvx').config(key, value, { envKeysFile: '../../.env.keys'} })
*/
envKeysFile?: string;
/**
* Set a .env convention (available conventions: 'nextjs, flow')
*/
convention?: string;
/**
* Specify whether the variable has to be encrypted
* @default true
* @example require('@dotenvx/dotenvx').config(key, value, { encrypt: false } })
*/
encrypt?: boolean;
/**
* Turn off Dotenvx Ops features - https://dotenvx.com/ops
*
* @default false
* @example require('@dotenvx/dotenvx').set(key, value, { opsOff: true })
*/
opsOff?: boolean;
}
export type SetProcessedEnv = {
key: string;
value: string;
filepath: string;
envFilepath: string;
envSrc: string;
changed: boolean;
encryptedValue?: string;
publicKey?: string;
privateKey?: string;
privateKeyAdded?: boolean;
privateKeyName?: string;
error?: Error;
};
export type SetOutput = {
processedEnvs: SetProcessedEnv[];
changedFilepaths: string[];
unchangedFilepaths: string[];
};
/**
* Set a single environment variable.
*
* @see https://dotenvx.com/docs
* @param key - KEY
* @param value - value
* @param options - additional options. example: `{ encrypt: false }`
*/
export function set(
key: string,
value: string,
options?: SetOptions
): SetOutput;
export interface GetOptions {
/**
* Suppress specific errors like MISSING_ENV_FILE. The error keys can be found
* in src/lib/helpers/errors.js
* @default []
* @example require('@dotenvx/dotenvx').get('KEY', { ignore: ['MISSING_ENV_FILE'] })
*/
ignore?: string[];
/**
* Override any environment variables that have already been set on your machine with values from your .env file.
* @default false
* @example require('@dotenvx/dotenvx').get('KEY', { overload: true })
* @alias overload
*/
overload?: boolean;
/**
* Customize the path to your .env.keys file. This is useful with monorepos.
* @default []
* @example require('@dotenvx/dotenvx').get('KEY', { envKeysFile: '../../.env.keys'} })
*/
envKeysFile?: string;
/**
* Throw immediately if an error is encountered - like a missing .env file.
* @default false
* @example require('@dotenvx/dotenvx').get('KEY', { strict: true })
*/
strict?: boolean;
/**
* Turn off Dotenvx Ops features - https://dotenvx.com/ops
*
* @default false
* @example require('@dotenvx/dotenvx').get('KEY', { opsOff: true })
*/
opsOff?: boolean;
}
/**
* Get a single environment variable.
*
* @see https://dotenvx.com/docs
* @param key - KEY
* @param options - additional options. example: `{ overload: true }`
*/
export function get(
key: string,
options?: GetOptions
): string;
/**
* List all env files in the current working directory
*
* @param directory - current working directory
* @param envFile - glob pattern to match env files
* @param excludeEnvFile - glob pattern to exclude env files
*/
export function ls(
directory: string,
envFile: string | string[],
excludeEnvFile: string | string[]
): string[];
export type GenExampleOutput = {
envExampleFile: string;
envFile: string | string[];
exampleFilepath: string;
addedKeys: string[];
injected: Record<string, string>;
preExisted: Record<string, string>;
};
/**
* Generate an example .env file
*
* @param directory - current working directory
* @param envFile - path to the .env file(s)
*/
export function genexample(
directory: string,
envFile: string
): GenExampleOutput;

314
node_modules/@dotenvx/dotenvx/src/lib/main.js generated vendored Normal file
View File

@@ -0,0 +1,314 @@
// @ts-check
const path = require('path')
// shared
const { setLogLevel, setLogName, setLogVersion, logger } = require('./../shared/logger')
const { getColor, bold } = require('./../shared/colors')
// services
const Ls = require('./services/ls')
const Run = require('./services/run')
const Sets = require('./services/sets')
const Get = require('./services/get')
const Keypair = require('./services/keypair')
const Genexample = require('./services/genexample')
// helpers
const buildEnvs = require('./helpers/buildEnvs')
const Parse = require('./helpers/parse')
const fsx = require('./helpers/fsx')
const localDisplayPath = require('./helpers/localDisplayPath')
/** @type {import('./main').config} */
const config = function (options = {}) {
// allow user to set processEnv to write to
let processEnv = process.env
if (options && options.processEnv != null) {
processEnv = options.processEnv
}
// overload
const overload = options.overload || options.override
// ignore
const ignore = options.ignore || []
// strict
const strict = options.strict
// envKeysFile
const envKeysFile = options.envKeysFile
// dotenvx-ops related
const opsOn = options.opsOff !== true
if (options) {
setLogLevel(options)
setLogName(options)
setLogVersion(options)
}
try {
const envs = buildEnvs(options)
const {
processedEnvs,
readableFilepaths,
uniqueInjectedKeys
} = new Run(envs, overload, processEnv, envKeysFile, opsOn).run()
if (opsOn) {
// removed radar feature for now. contact me at mot@dotenvx.com if still needed for your organization.
// try { new Ops().observe({ beforeEnv, processedEnvs, afterEnv }) } catch {}
}
let lastError
/** @type {Record<string, string>} */
const parsedAll = {}
for (const processedEnv of processedEnvs) {
if (processedEnv.type === 'envFile') {
logger.verbose(`loading env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
}
for (const error of processedEnv.errors || []) {
if (ignore.includes(error.code)) {
logger.verbose(`ignored: ${error.message}`)
continue // ignore error
}
if (strict) throw error // throw if strict and not ignored
lastError = error // surface later in { error }
if (error.code === 'MISSING_ENV_FILE') {
if (!options.convention) { // do not output error for conventions (too noisy)
logger.error(error.messageWithHelp)
}
} else {
logger.error(error.messageWithHelp)
}
}
Object.assign(parsedAll, processedEnv.injected || {})
Object.assign(parsedAll, processedEnv.preExisted || {}) // preExisted 'wins'
// debug parsed
logger.debug(processedEnv.parsed)
// verbose/debug injected key/value
for (const [key, value] of Object.entries(processedEnv.injected || {})) {
logger.verbose(`${key} set`)
logger.debug(`${key} set to ${value}`)
}
// verbose/debug preExisted key/value
for (const [key, value] of Object.entries(processedEnv.preExisted || {})) {
logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
}
}
let msg = `injecting env (${uniqueInjectedKeys.length})`
if (readableFilepaths.length > 0) {
msg += ` from ${readableFilepaths.join(', ')}`
}
logger.successv(msg)
if (lastError) {
return { parsed: parsedAll, error: lastError }
} else {
return { parsed: parsedAll }
}
} catch (error) {
if (strict) throw error // throw immediately if strict
logger.error(error.messageWithHelp)
return { parsed: {}, error }
}
}
/** @type {import('./main').parse} */
const parse = function (src, options = {}) {
// allow user to set processEnv to read from
let processEnv = process.env
if (options && options.processEnv != null) {
processEnv = options.processEnv
}
// private decryption key
const privateKey = options.privateKey || null
// overload
const overload = options.overload || options.override
const { parsed, errors } = new Parse(src, privateKey, processEnv, overload).run()
// display any errors
for (const error of errors) {
logger.error(error.messageWithHelp)
}
return parsed
}
/* @type {import('./main').set} */
const set = function (key, value, options = {}) {
// encrypt
let encrypt = true
if (options.plain) {
encrypt = false
} else if (options.encrypt === false) {
encrypt = false
}
if (options) {
setLogLevel(options)
setLogName(options)
setLogVersion(options)
}
const envs = buildEnvs(options)
const envKeysFilepath = options.envKeysFile
const opsOn = options.opsOff !== true
const {
processedEnvs,
changedFilepaths,
unchangedFilepaths
} = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn).run()
let withEncryption = ''
if (encrypt) {
withEncryption = ' with encryption'
}
for (const processedEnv of processedEnvs) {
logger.verbose(`setting for ${processedEnv.envFilepath}`)
if (processedEnv.error) {
const error = processedEnv.error
const message = error.messageWithHelp || (error.help ? `${error.message}. ${error.help}` : error.message)
logger.warn(message)
} else {
fsx.writeFileX(processedEnv.filepath, processedEnv.envSrc)
logger.verbose(`${processedEnv.key} set${withEncryption} (${processedEnv.envFilepath})`)
logger.debug(`${processedEnv.key} set${withEncryption} to ${processedEnv.value} (${processedEnv.envFilepath})`)
}
}
const keyAddedEnv = processedEnvs.find((processedEnv) => processedEnv.privateKeyAdded)
const keyAddedSuffix = keyAddedEnv ? ` + key (${localDisplayPath(keyAddedEnv.envKeysFilepath)})` : ''
if (changedFilepaths.length > 0) {
if (encrypt) {
logger.success(`◈ encrypted ${key} (${changedFilepaths.join(',')})${keyAddedSuffix}`)
} else {
logger.success(`◇ set ${key} (${changedFilepaths.join(',')})`)
}
} else if (encrypt && keyAddedEnv) {
const keyAddedEnvFilepath = keyAddedEnv.envFilepath || changedFilepaths[0] || '.env'
logger.success(`◈ encrypted ${key} (${keyAddedEnvFilepath})${keyAddedSuffix}`)
} else if (unchangedFilepaths.length > 0) {
logger.info(`○ no changes (${unchangedFilepaths})`)
} else {
// do nothing
}
// intentionally quiet: success line communicates key creation
return {
processedEnvs,
changedFilepaths,
unchangedFilepaths
}
}
/* @type {import('./main').get} */
const get = function (key, options = {}) {
const envs = buildEnvs(options)
const opsOn = options.opsOff !== true
// ignore
const ignore = options.ignore || []
const { parsed, errors } = new Get(key, envs, options.overload, options.all, options.envKeysFile, opsOn).run()
for (const error of errors || []) {
if (ignore.includes(error.code)) {
continue // ignore error
}
if (options.strict) throw error // throw immediately if strict
logger.error(error.messageWithHelp)
}
if (key) {
const single = parsed[key]
if (single === undefined) {
return undefined
} else {
return single
}
} else {
if (options.format === 'eval') {
let inline = ''
for (const [key, value] of Object.entries(parsed)) {
inline += `${key}=${escape(value)}\n`
}
inline = inline.trim()
return inline
} else if (options.format === 'shell') {
let inline = ''
for (const [key, value] of Object.entries(parsed)) {
inline += `${key}=${value} `
}
inline = inline.trim()
return inline
} else {
return parsed
}
}
}
/** @type {import('./main').ls} */
const ls = function (directory, envFile, excludeEnvFile) {
return new Ls(directory, envFile, excludeEnvFile).run()
}
/** @type {import('./main').genexample} */
const genexample = function (directory, envFile) {
return new Genexample(directory, envFile).run()
}
/** @type {import('./main').keypair} */
const keypair = function (envFile, key, envKeysFile = null, opsOff = false) {
const opsOn = opsOff !== true
const keypairs = new Keypair(envFile, envKeysFile, opsOn).run()
if (key) {
return keypairs[key]
} else {
return keypairs
}
}
module.exports = {
// dotenv proxies
config,
parse,
// actions related
set,
get,
ls,
keypair,
genexample,
// expose for libs depending on @dotenvx/dotenvx - like dotenvx-ops
setLogLevel,
logger,
getColor,
bold
}

View File

@@ -0,0 +1,143 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const picomatch = require('picomatch')
const TYPE_ENV_FILE = 'envFile'
const Errors = require('./../helpers/errors')
const {
determine
} = require('./../helpers/envResolution')
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
const {
decryptKeyValue,
isEncrypted
} = require('./../helpers/cryptography')
const replace = require('./../helpers/replace')
const dotenvParse = require('./../helpers/dotenvParse')
const detectEncoding = require('./../helpers/detectEncoding')
class Decrypt {
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
this.envs = determine(envs, process.env)
this.key = key
this.excludeKey = excludeKey
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
this.processedEnvs = []
this.changedFilepaths = new Set()
this.unchangedFilepaths = new Set()
}
run () {
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
this.keys = this._keys()
const excludeKeys = this._excludeKeys()
this.exclude = picomatch(excludeKeys)
this.include = picomatch(this.keys, { ignore: excludeKeys })
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._decryptEnvFile(env.value)
}
}
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
_decryptEnvFile (envFilepath) {
const row = {}
row.keys = []
row.type = TYPE_ENV_FILE
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
try {
const encoding = detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenvParse(envSrc)
const { privateKeyName } = keyNames(envFilepath)
const { privateKeyValue } = keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
row.privateKey = privateKeyValue
row.privateKeyName = privateKeyName
row.changed = false // track possible changes
for (const [key, value] of Object.entries(envParsed)) {
// key excluded - don't decrypt it
if (this.exclude(key)) {
continue
}
// key effectively excluded (by not being in the list of includes) - don't decrypt it
if (this.keys.length > 0 && !this.include(key)) {
continue
}
const encrypted = isEncrypted(value)
if (encrypted) {
row.keys.push(key) // track key(s)
const decryptedValue = decryptKeyValue(key, value, privateKeyName, privateKeyValue)
// once newSrc is built write it out
envSrc = replace(envSrc, key, decryptedValue)
row.changed = true // track change
}
}
row.envSrc = envSrc
if (row.changed) {
this.changedFilepaths.add(envFilepath)
} else {
this.unchangedFilepaths.add(envFilepath)
}
} catch (e) {
if (e.code === 'ENOENT') {
row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
} else {
row.error = e
}
}
this.processedEnvs.push(row)
}
_keys () {
if (!Array.isArray(this.key)) {
return [this.key]
}
return this.key
}
_excludeKeys () {
if (!Array.isArray(this.excludeKey)) {
return [this.excludeKey]
}
return this.excludeKey
}
}
module.exports = Decrypt

View File

@@ -0,0 +1,173 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const picomatch = require('picomatch')
const TYPE_ENV_FILE = 'envFile'
const Errors = require('./../helpers/errors')
const {
determine
} = require('./../helpers/envResolution')
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
const {
encryptValue,
isEncrypted,
isPublicKey,
provision,
provisionWithPrivateKey
} = require('./../helpers/cryptography')
const replace = require('./../helpers/replace')
const dotenvParse = require('./../helpers/dotenvParse')
const detectEncoding = require('./../helpers/detectEncoding')
class Encrypt {
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
this.envs = determine(envs, process.env)
this.key = key
this.excludeKey = excludeKey
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
this.processedEnvs = []
this.changedFilepaths = new Set()
this.unchangedFilepaths = new Set()
}
run () {
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
this.keys = this._keys()
const excludeKeys = this._excludeKeys()
this.exclude = picomatch(excludeKeys)
this.include = picomatch(this.keys, { ignore: excludeKeys })
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._encryptEnvFile(env.value)
}
}
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
_encryptEnvFile (envFilepath) {
const row = {}
row.keys = []
row.type = TYPE_ENV_FILE
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
try {
const encoding = detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenvParse(envSrc)
let publicKey
let privateKey
const { publicKeyName, privateKeyName } = keyNames(envFilepath)
const { publicKeyValue, privateKeyValue } = keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
// first pass - provision
if (!privateKeyValue && !publicKeyValue) {
const prov = provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
envSrc = prov.envSrc
publicKey = prov.publicKey
privateKey = prov.privateKey
row.privateKeyAdded = prov.privateKeyAdded
row.envKeysFilepath = prov.envKeysFilepath
} else if (privateKeyValue) {
const prov = provisionWithPrivateKey({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, privateKeyValue, publicKeyValue, publicKeyName })
publicKey = prov.publicKey
privateKey = prov.privateKey
envSrc = prov.envSrc
} else if (publicKeyValue) {
publicKey = publicKeyValue
}
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
}
// key effectively excluded (by not being in the list of includes) - don't encrypt it
if (this.keys.length > 0 && !this.include(key)) {
continue
}
const encrypted = isEncrypted(value) || isPublicKey(key)
if (!encrypted) {
row.keys.push(key) // track key(s)
let encryptedValue
try {
encryptedValue = encryptValue(value, publicKey)
} catch {
throw new Errors({ publicKeyName, publicKey }).invalidPublicKey()
}
// once newSrc is built write it out
envSrc = replace(envSrc, key, encryptedValue)
row.changed = true // track change
}
}
row.envSrc = envSrc
if (row.changed) {
this.changedFilepaths.add(envFilepath)
} else {
this.unchangedFilepaths.add(envFilepath)
}
} catch (e) {
if (e.code === 'ENOENT') {
row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
} else {
row.error = e
}
}
this.processedEnvs.push(row)
}
_keys () {
if (!Array.isArray(this.key)) {
return [this.key]
}
return this.key
}
_excludeKeys () {
if (!Array.isArray(this.excludeKey)) {
return [this.excludeKey]
}
return this.excludeKey
}
}
module.exports = Encrypt

View File

@@ -0,0 +1,101 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const Errors = require('../helpers/errors')
const findEnvFiles = require('../helpers/findEnvFiles')
const replace = require('../helpers/replace')
const dotenvParse = require('../helpers/dotenvParse')
class Genexample {
constructor (directory = '.', envFile) {
this.directory = directory
this.envFile = envFile || findEnvFiles(directory)
this.exampleFilename = '.env.example'
this.exampleFilepath = path.resolve(this.directory, this.exampleFilename)
}
run () {
if (this.envFile.length < 1) {
throw new Errors().missingEnvFiles()
}
const keys = new Set()
const addedKeys = new Set()
const envFilepaths = this._envFilepaths()
/** @type {Record<string, string>} */
const injected = {}
/** @type {Record<string, string>} */
const preExisted = {}
let exampleSrc = `# ${this.exampleFilename} - generated with dotenvx\n`
for (const envFilepath of envFilepaths) {
const filepath = path.resolve(this.directory, envFilepath)
if (!fsx.existsSync(filepath)) {
const error = new Errors({ envFilepath, filepath }).missingEnvFile()
error.help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx genexample]`
throw error
}
// get the original src
let src = fsx.readFileX(filepath)
const parsed = dotenvParse(src)
for (const key in parsed) {
// used later
keys.add(key)
// once newSrc is built write it out
src = replace(src, key, '') // empty value
}
exampleSrc += `\n${src}`
}
if (!fsx.existsSync(this.exampleFilepath)) {
// it doesn't exist so just write this first generated one
// exampleSrc - already written to from the prior loop
for (const key of [...keys]) {
// every key is added since it's the first time generating .env.example
addedKeys.add(key)
injected[key] = ''
}
} else {
// 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)
exampleSrc = fsx.readFileX(this.exampleFilepath)
const parsed = dotenvParse(exampleSrc)
for (const key of [...keys]) {
if (key in parsed) {
preExisted[key] = parsed[key]
} else {
exampleSrc += `${key}=''\n`
addedKeys.add(key)
injected[key] = ''
}
}
}
return {
envExampleFile: exampleSrc,
envFile: this.envFile,
exampleFilepath: this.exampleFilepath,
addedKeys: [...addedKeys],
injected,
preExisted
}
}
_envFilepaths () {
if (!Array.isArray(this.envFile)) {
return [this.envFile]
}
return this.envFile
}
}
module.exports = Genexample

59
node_modules/@dotenvx/dotenvx/src/lib/services/get.js generated vendored Normal file
View File

@@ -0,0 +1,59 @@
const Run = require('./run')
const Errors = require('./../helpers/errors')
class Get {
constructor (key, envs = [], overload = false, all = false, envKeysFilepath = null, opsOn = true) {
this.key = key
this.envs = envs
this.overload = overload
this.all = all
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
}
run () {
const processEnv = { ...process.env }
const { processedEnvs } = new Run(this.envs, this.overload, processEnv, this.envKeysFilepath, this.opsOn).run()
const errors = []
for (const processedEnv of processedEnvs) {
for (const error of processedEnv.errors) {
errors.push(error)
}
}
if (this.key) {
const parsed = {}
const value = processEnv[this.key]
parsed[this.key] = value
if (value === undefined) {
errors.push(new Errors({ key: this.key }).missingKey())
}
return { parsed, errors }
} else {
// if user wants to return ALL envs (even prior set on machine)
if (this.all) {
return { parsed: processEnv, errors }
}
// typical scenario - return only envs that were identified in the .env file
// iterate over all processedEnvs.parsed and grab from processEnv
/** @type {Record<string, string>} */
const parsed = {}
for (const processedEnv of processedEnvs) {
// parsed means we saw the key in a file or --env flag. this effectively filters out any preset machine envs - while still respecting complex evaluating, expansion, and overload. in other words, the value might be the machine value because the key was displayed in a .env file
if (processedEnv.parsed) {
for (const key of Object.keys(processedEnv.parsed)) {
parsed[key] = processEnv[key]
}
}
}
return { parsed, errors }
}
}
}
module.exports = Get

View File

@@ -0,0 +1,37 @@
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
class Keypair {
constructor (envFile = '.env', envKeysFilepath = null, opsOn = false) {
this.envFile = envFile
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
}
run () {
const out = {}
const filepaths = this._filepaths()
for (const filepath of filepaths) {
const { publicKeyName, privateKeyName } = keyNames(filepath)
const { publicKeyValue, privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
out[publicKeyName] = publicKeyValue
out[privateKeyName] = privateKeyValue
}
return out
}
_filepaths () {
if (!Array.isArray(this.envFile)) {
return [this.envFile]
}
return this.envFile
}
}
module.exports = Keypair

57
node_modules/@dotenvx/dotenvx/src/lib/services/ls.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
const { fdir: Fdir } = require('fdir')
const path = require('path')
const picomatch = require('picomatch')
class Ls {
constructor (directory = './', envFile = ['.env*'], excludeEnvFile = []) {
this.ignore = ['node_modules/**', '.git/**']
this.cwd = path.resolve(directory)
this.envFile = envFile
this.excludeEnvFile = excludeEnvFile
}
run () {
return this._filepaths()
}
_filepaths () {
const exclude = picomatch(this._exclude())
const include = picomatch(this._patterns(), {
ignore: this._exclude()
})
return new Fdir()
.withRelativePaths()
.exclude((dir, path) => exclude(path))
.filter((path) => include(path))
.crawl(this.cwd)
.sync()
}
_patterns () {
if (!Array.isArray(this.envFile)) {
return [`**/${this.envFile}`]
}
return this.envFile.map(part => `**/${part}`)
}
_excludePatterns () {
if (!Array.isArray(this.excludeEnvFile)) {
return [`**/${this.excludeEnvFile}`]
}
return this.excludeEnvFile.map(part => `**/${part}`)
}
_exclude () {
if (this._excludePatterns().length > 0) {
return this.ignore.concat(this._excludePatterns())
} else {
return this.ignore
}
}
}
module.exports = Ls

View File

@@ -0,0 +1,91 @@
/* istanbul ignore file */
const fsx = require('./../helpers/fsx')
const path = require('path')
const ignore = require('ignore')
const Ls = require('../services/ls')
const Errors = require('../helpers/errors')
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
const packageJson = require('./../helpers/packageJson')
const MISSING_DOCKERIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
class Prebuild {
constructor (directory = './') {
// args
this.directory = directory
this.excludeEnvFile = ['test/**', 'tests/**', 'spec/**', 'specs/**', 'pytest/**', 'test_suite/**']
}
run () {
let count = 0
const warnings = []
let dockerignore = MISSING_DOCKERIGNORE
// 1. check for .dockerignore file
if (!fsx.existsSync('.dockerignore')) {
const warning = new Errors({
message: `[dotenvx@${packageJson.version}][prebuild] .dockerignore missing`,
help: 'fix: [touch .dockerignore]'
}).custom()
warnings.push(warning)
} else {
dockerignore = fsx.readFileX('.dockerignore')
}
// 2. check .env* files against .dockerignore file
const ig = ignore().add(dockerignore)
const lsService = new Ls(this.directory, undefined, this.excludeEnvFile)
const dotenvFiles = lsService.run()
dotenvFiles.forEach(_file => {
count += 1
const file = path.join(this.directory, _file) // to handle when directory argument passed
// check if that file is being ignored
if (ig.ignores(file)) {
if (file === '.env.example' || file === '.env.x') {
const warning = new Errors({
message: `[dotenvx@${packageJson.version}][prebuild] ${file} (currently ignored but should not be)`,
help: `fix: [dotenvx ext gitignore --pattern !${file}]`
}).custom()
warnings.push(warning)
}
} else {
if (file !== '.env.example' && file !== '.env.x') {
const src = fsx.readFileX(file)
const encrypted = isFullyEncrypted(src)
// if contents are encrypted don't raise an error
if (!encrypted) {
let errorMsg = `[dotenvx@${packageJson.version}][prebuild] ${file} not protected (encrypted or dockerignored)`
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx ext gitignore --pattern ${file}]`
if (file.includes('.env.keys')) {
errorMsg = `[dotenvx@${packageJson.version}][prebuild] ${file} not protected (dockerignored)`
errorHelp = `fix: [dotenvx ext gitignore --pattern ${file}]`
}
throw new Errors({ message: errorMsg, help: errorHelp }).custom()
}
}
}
})
let successMessage = `[dotenvx@${packageJson.version}][prebuild] .env files (${count}) protected (encrypted or dockerignored)`
if (count === 0) {
successMessage = `[dotenvx@${packageJson.version}][prebuild] zero .env files`
}
if (warnings.length > 0) {
successMessage += ` with warnings (${warnings.length})`
}
return {
successMessage,
warnings
}
}
}
module.exports = Prebuild

View File

@@ -0,0 +1,137 @@
/* istanbul ignore file */
const fsx = require('./../helpers/fsx')
const path = require('path')
const ignore = require('ignore')
const Ls = require('../services/ls')
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
const packageJson = require('./../helpers/packageJson')
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
const Errors = require('./../helpers/errors')
const childProcess = require('child_process')
const MISSING_GITIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
class Precommit {
constructor (directory = './', options = {}) {
// args
this.directory = directory
// options
this.install = options.install
this.excludeEnvFile = ['test/**', 'tests/**', 'spec/**', 'specs/**', 'pytest/**', 'test_suite/**']
}
run () {
if (this.install) {
const {
successMessage
} = this._installPrecommitHook()
return {
successMessage,
warnings: []
}
} else {
let count = 0
const warnings = []
let gitignore = MISSING_GITIGNORE
// 1. check for .gitignore file
if (!fsx.existsSync('.gitignore')) {
const warning = new Errors({
message: `[dotenvx@${packageJson.version}][precommit] .gitignore missing`,
help: 'fix: [touch .gitignore]'
}).custom()
warnings.push(warning)
} else {
gitignore = fsx.readFileX('.gitignore')
}
// 2. check .env* files against .gitignore file
const ig = ignore().add(gitignore)
const lsService = new Ls(this.directory, undefined, this.excludeEnvFile)
const dotenvFiles = lsService.run()
dotenvFiles.forEach(_file => {
count += 1
const file = path.join(this.directory, _file) // to handle when directory argument passed
// check if file is going to be committed
if (this._isFileToBeCommitted(file)) {
// check if that file is being ignored
if (ig.ignores(file)) {
if (file === '.env.example' || file === '.env.x') {
const warning = new Errors({
message: `[dotenvx@${packageJson.version}][precommit] ${file} (currently ignored but should not be)`,
help: `fix: [dotenvx ext gitignore --pattern !${file}]`
}).custom()
warnings.push(warning)
}
} else {
if (file !== '.env.example' && file !== '.env.x') {
const src = fsx.readFileX(file)
const encrypted = isFullyEncrypted(src)
// if contents are encrypted don't raise an error
if (!encrypted) {
let errorMsg = `[dotenvx@${packageJson.version}][precommit] ${file} not protected (encrypted or gitignored)`
let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx ext gitignore --pattern ${file}]`
if (file.includes('.env.keys')) {
errorMsg = `[dotenvx@${packageJson.version}][precommit] ${file} not protected (gitignored)`
errorHelp = `fix: [dotenvx ext gitignore --pattern ${file}]`
}
throw new Errors({ message: errorMsg, help: errorHelp }).custom()
}
}
}
}
})
let successMessage = `[dotenvx@${packageJson.version}][precommit] .env files (${count}) protected (encrypted or gitignored)`
if (count === 0) {
successMessage = `[dotenvx@${packageJson.version}][precommit] zero .env files`
}
if (warnings.length > 0) {
successMessage += ` with warnings (${warnings.length})`
}
return {
successMessage,
warnings
}
}
}
_isInGitRepo () {
try {
childProcess.execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
return true
} catch {
return false
}
}
_isFileToBeCommitted (filePath) {
try {
if (!this._isInGitRepo()) {
// consider file to be committed if there is an error (not a git repo)
return true
}
const output = childProcess.execSync('git diff HEAD --name-only').toString()
const files = output.split('\n')
return files.includes(filePath)
} catch (error) {
// consider file to be committed if there is an error (not using git)
return true
}
}
_installPrecommitHook () {
return new InstallPrecommitHook().run()
}
}
module.exports = Precommit

View File

@@ -0,0 +1,184 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const picomatch = require('picomatch')
const TYPE_ENV_FILE = 'envFile'
const Errors = require('./../helpers/errors')
const {
determine
} = require('./../helpers/envResolution')
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
const {
opsKeypair,
localKeypair,
encryptValue,
decryptKeyValue,
isEncrypted
} = require('./../helpers/cryptography')
const append = require('./../helpers/append')
const replace = require('./../helpers/replace')
const dotenvParse = require('./../helpers/dotenvParse')
const detectEncoding = require('./../helpers/detectEncoding')
class Rotate {
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
this.envs = determine(envs, process.env)
this.key = key
this.excludeKey = excludeKey
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
this.processedEnvs = []
this.changedFilepaths = new Set()
this.unchangedFilepaths = new Set()
this.envKeysSources = {}
}
run () {
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
this.keys = this._keys()
const excludeKeys = this._excludeKeys()
this.exclude = picomatch(excludeKeys)
this.include = picomatch(this.keys, { ignore: excludeKeys })
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._rotateEnvFile(env.value)
}
}
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
_rotateEnvFile (envFilepath) {
const row = {}
row.keys = []
row.type = TYPE_ENV_FILE
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
try {
const encoding = detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenvParse(envSrc)
const { publicKeyName, privateKeyName } = keyNames(envFilepath)
const { privateKeyValue } = keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
let newPublicKey
let newPrivateKey
let envKeysFilepath
let envKeysSrc
if (this.opsOn) {
const kp = opsKeypair()
newPublicKey = kp.publicKey
newPrivateKey = kp.privateKey
row.privateKeyAdded = false // TODO: change to localPrivateKeyAdded
} else {
envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
if (this.envKeysFilepath) {
envKeysFilepath = path.resolve(this.envKeysFilepath)
}
row.envKeysFilepath = envKeysFilepath
this.envKeysSources[envKeysFilepath] ||= fsx.readFileX(envKeysFilepath, { encoding: detectEncoding(envKeysFilepath) })
envKeysSrc = this.envKeysSources[envKeysFilepath]
const kp = localKeypair()
newPublicKey = kp.publicKey
newPrivateKey = kp.privateKey
row.privateKeyAdded = true
}
// .env
envSrc = replace(envSrc, publicKeyName, newPublicKey) // replace publicKey
row.changed = true // track change
for (const [key, value] of Object.entries(envParsed)) { // re-encrypt each individual key
// key excluded - don't re-encrypt it
if (this.exclude(key)) {
continue
}
// key effectively excluded (by not being in the list of includes) - don't re-encrypt it
if (this.keys.length > 0 && !this.include(key)) {
continue
}
if (isEncrypted(value)) { // only re-encrypt those already encrypted
row.keys.push(key) // track key(s)
const decryptedValue = decryptKeyValue(key, value, privateKeyName, privateKeyValue) // get decrypted value
let encryptedValue
try {
encryptedValue = encryptValue(decryptedValue, newPublicKey) // encrypt with the new publicKey
} catch {
throw new Errors({ publicKeyName, publicKey: newPublicKey }).invalidPublicKey()
}
envSrc = replace(envSrc, key, encryptedValue)
}
}
row.envSrc = envSrc
row.privateKeyName = privateKeyName
row.privateKey = newPrivateKey
if (!this.opsOn) {
// keys src only for ops
envKeysSrc = append(envKeysSrc, privateKeyName, newPrivateKey) // append privateKey
this.envKeysSources[envKeysFilepath] = envKeysSrc
row.envKeysSrc = envKeysSrc
}
this.changedFilepaths.add(envFilepath)
} catch (e) {
if (e.code === 'ENOENT') {
row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
} else {
row.error = e
}
}
this.processedEnvs.push(row)
}
_keys () {
if (!Array.isArray(this.key)) {
return [this.key]
}
return this.key
}
_excludeKeys () {
if (!Array.isArray(this.excludeKey)) {
return [this.excludeKey]
}
return this.excludeKey
}
}
module.exports = Rotate

145
node_modules/@dotenvx/dotenvx/src/lib/services/run.js generated vendored Normal file
View File

@@ -0,0 +1,145 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const TYPE_ENV = 'env'
const TYPE_ENV_FILE = 'envFile'
const Parse = require('./../helpers/parse')
const Errors = require('./../helpers/errors')
const detectEncoding = require('./../helpers/detectEncoding')
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
const {
determine
} = require('./../helpers/envResolution')
class Run {
constructor (envs = [], overload = false, processEnv = process.env, envKeysFilepath = null, opsOn = false) {
this.envs = determine(envs, processEnv)
this.overload = overload
this.processEnv = processEnv
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
this.processedEnvs = []
this.readableFilepaths = new Set()
this.readableStrings = new Set()
this.uniqueInjectedKeys = new Set()
this.beforeEnv = { ...this.processEnv }
}
run () {
// example
// envs [
// { type: 'env', value: 'HELLO=one' },
// { type: 'envFile', value: '.env' },
// { type: 'env', value: 'HELLO=three' }
// ]
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._injectEnvFile(env.value)
} else if (env.type === TYPE_ENV) {
this._injectEnv(env.value)
}
}
return {
processedEnvs: this.processedEnvs,
readableStrings: [...this.readableStrings],
readableFilepaths: [...this.readableFilepaths],
uniqueInjectedKeys: [...this.uniqueInjectedKeys],
beforeEnv: this.beforeEnv,
afterEnv: { ...this.processEnv }
}
}
_injectEnv (env) {
const row = {}
row.type = TYPE_ENV
row.string = env
try {
const {
parsed,
errors,
injected,
preExisted
} = new Parse(env, null, this.processEnv, this.overload).run()
row.parsed = parsed
row.errors = errors
row.injected = injected
row.preExisted = preExisted
this.inject(row.parsed) // inject
this.readableStrings.add(env)
for (const key of Object.keys(injected)) {
this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
}
} catch (e) {
row.errors = [e]
}
this.processedEnvs.push(row)
}
_injectEnvFile (envFilepath) {
const row = {}
row.type = TYPE_ENV_FILE
row.filepath = envFilepath
const filepath = path.resolve(envFilepath)
try {
const encoding = detectEncoding(filepath)
const src = fsx.readFileX(filepath, { encoding })
this.readableFilepaths.add(envFilepath)
const { privateKeyName } = keyNames(filepath)
const { privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
const {
parsed,
errors,
injected,
preExisted
} = new Parse(src, privateKeyValue, this.processEnv, this.overload, privateKeyName).run()
row.privateKeyName = privateKeyName
row.privateKey = privateKeyValue
row.src = src
row.parsed = parsed
row.errors = errors
row.injected = injected
row.preExisted = preExisted
this.inject(row.parsed) // inject
for (const key of Object.keys(injected)) {
this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
}
} catch (e) {
if (e.code === 'ENOENT' || e.code === 'EISDIR') {
row.errors = [new Errors({ envFilepath, filepath }).missingEnvFile()]
} else {
row.errors = [e]
}
}
this.processedEnvs.push(row)
}
inject (parsed) {
for (const key of Object.keys(parsed)) {
this.processEnv[key] = parsed[key] // inject to process.env
}
}
}
module.exports = Run

143
node_modules/@dotenvx/dotenvx/src/lib/services/sets.js generated vendored Normal file
View File

@@ -0,0 +1,143 @@
const fsx = require('./../helpers/fsx')
const path = require('path')
const TYPE_ENV_FILE = 'envFile'
const Errors = require('./../helpers/errors')
const {
determine
} = require('./../helpers/envResolution')
const {
keyNames,
keyValues
} = require('./../helpers/keyResolution')
const {
encryptValue,
decryptKeyValue,
isEncrypted,
provision,
provisionWithPrivateKey
} = require('./../helpers/cryptography')
const replace = require('./../helpers/replace')
const dotenvParse = require('./../helpers/dotenvParse')
const detectEncoding = require('./../helpers/detectEncoding')
class Sets {
constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false) {
this.envs = determine(envs, process.env)
this.key = key
this.value = value
this.encrypt = encrypt
this.envKeysFilepath = envKeysFilepath
this.opsOn = opsOn
this.processedEnvs = []
this.changedFilepaths = new Set()
this.unchangedFilepaths = new Set()
this.readableFilepaths = new Set()
}
run () {
// example
// envs [
// { type: 'envFile', value: '.env' }
// ]
for (const env of this.envs) {
if (env.type === TYPE_ENV_FILE) {
this._setEnvFile(env.value)
}
}
return {
processedEnvs: this.processedEnvs,
changedFilepaths: [...this.changedFilepaths],
unchangedFilepaths: [...this.unchangedFilepaths]
}
}
_setEnvFile (envFilepath) {
const row = {}
row.key = this.key || null
row.value = this.value || null
row.type = TYPE_ENV_FILE
const filepath = path.resolve(envFilepath)
row.filepath = filepath
row.envFilepath = envFilepath
row.changed = false
try {
const encoding = detectEncoding(filepath)
let envSrc = fsx.readFileX(filepath, { encoding })
const envParsed = dotenvParse(envSrc)
row.originalValue = envParsed[row.key] || null
const wasPlainText = !isEncrypted(row.originalValue)
this.readableFilepaths.add(envFilepath)
if (this.encrypt) {
let publicKey
let privateKey
const { publicKeyName, privateKeyName } = keyNames(filepath)
const { publicKeyValue, privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
// first pass - provision
if (!privateKeyValue && !publicKeyValue) {
const prov = provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
envSrc = prov.envSrc
publicKey = prov.publicKey
privateKey = prov.privateKey
row.privateKeyAdded = prov.privateKeyAdded
row.envKeysFilepath = prov.envKeysFilepath
} else if (privateKeyValue) {
const prov = provisionWithPrivateKey({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, privateKeyValue, publicKeyValue, publicKeyName })
publicKey = prov.publicKey
privateKey = prov.privateKey
envSrc = prov.envSrc
if (row.originalValue) {
row.originalValue = decryptKeyValue(row.key, row.originalValue, privateKeyName, privateKey)
}
} else if (publicKeyValue) {
publicKey = publicKeyValue
}
row.publicKey = publicKey
row.privateKey = privateKey
try {
row.encryptedValue = encryptValue(this.value, publicKey)
} catch {
throw new Errors({ publicKeyName, publicKey }).invalidPublicKey()
}
row.privateKeyName = privateKeyName
}
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') {
row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
} else {
row.error = e
}
}
this.processedEnvs.push(row)
}
}
module.exports = Sets

67
node_modules/@dotenvx/dotenvx/src/shared/colors.js generated vendored Normal file
View File

@@ -0,0 +1,67 @@
const depth = require('../lib/helpers/colorDepth')
const Errors = require('../lib/helpers/errors')
const colors16 = new Map([
['amber', 33],
['blue', 34],
['gray', 37],
['green', 32],
['olive', 33],
['orangered', 33], // mapped to yellow/brown
['plum', 35], // mapped to magenta
['red', 31],
['electricblue', 36],
['dodgerblue', 36]
])
const colors256 = new Map([
['amber', 136],
['blue', 21],
['gray', 244],
['green', 34],
['olive', 142],
['orangered', 130], // burnished copper
['plum', 182],
['red', 124], // brighter garnet
['electricblue', 45],
['dodgerblue', 33]
])
const colorsTrueColor = new Map([
['amber', [236, 213, 63]],
['orangered', [138, 90, 43]], // #8A5A2B burnished copper
['red', [140, 35, 50]] // #8C2332 brighter garnet
])
function getColor (color) {
const colorDepth = depth.getColorDepth()
if (!colors256.has(color)) {
throw new Errors({ color }).invalidColor()
}
if (colorDepth >= 24 && colorsTrueColor.has(color)) {
const [r, g, b] = colorsTrueColor.get(color)
return (message) => `\x1b[38;2;${r};${g};${b}m${message}\x1b[39m`
}
if (colorDepth >= 8) {
const code = colors256.get(color)
return (message) => `\x1b[38;5;${code}m${message}\x1b[39m`
}
if (colorDepth >= 4) {
const code = colors16.get(color)
return (message) => `\x1b[${code}m${message}\x1b[39m`
}
return (message) => message
}
function bold (message) {
if (depth.getColorDepth() >= 4) {
return `\x1b[1m${message}\x1b[22m`
}
return message
}
module.exports = {
getColor,
bold
}

147
node_modules/@dotenvx/dotenvx/src/shared/logger.js generated vendored Normal file
View File

@@ -0,0 +1,147 @@
const packageJson = require('../lib/helpers/packageJson')
const Errors = require('../lib/helpers/errors')
const { getColor, bold } = require('./colors')
const levels = {
error: 0,
warn: 1,
success: 2,
successv: 2,
info: 2,
help: 2,
verbose: 4,
debug: 5,
silly: 6
}
const error = (m) => bold(getColor('red')(`${m}`))
const warn = (m) => getColor('orangered')(`${m}`)
const success = getColor('amber')
const successv = getColor('amber')
const info = getColor('gray')
const help = getColor('dodgerblue')
const verbose = getColor('plum')
const debug = getColor('plum')
let currentLevel = levels.info // default log level
let currentName = 'dotenvx' // default logger name
let currentVersion = packageJson.version // default logger version
function stderr (level, message) {
const formattedMessage = formatMessage(level, message)
console.error(formattedMessage)
}
function stdout (level, message) {
if (levels[level] === undefined) {
throw new Errors({ level }).missingLogLevel()
}
if (levels[level] <= currentLevel) {
const formattedMessage = formatMessage(level, message)
console.log(formattedMessage)
}
}
function formatMessage (level, message) {
const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
switch (level.toLowerCase()) {
// errors
case 'error':
return error(formattedMessage)
// warns
case 'warn':
return warn(formattedMessage)
// successes
case 'success':
return success(formattedMessage)
case 'successv': // success with 'version'
return successv(`[${currentName}@${currentVersion}] ${formattedMessage}`)
// info
case 'info':
return info(formattedMessage)
// help
case 'help':
return help(formattedMessage)
// verbose
case 'verbose':
return verbose(formattedMessage)
// debug
case 'debug':
return debug(formattedMessage)
}
}
const logger = {
// track level
level: 'info',
// errors
error: (msg) => stderr('error', msg),
// warns
warn: (msg) => stdout('warn', msg),
// success
success: (msg) => stdout('success', msg),
successv: (msg) => stdout('successv', msg),
// info
info: (msg) => stdout('info', msg),
// help
help: (msg) => stdout('help', msg),
// verbose
verbose: (msg) => stdout('verbose', msg),
// debug
debug: (msg) => stdout('debug', msg),
setLevel: (level) => {
if (levels[level] !== undefined) {
currentLevel = levels[level]
logger.level = level
}
},
setName: (name) => {
currentName = name
logger.name = name
},
setVersion: (version) => {
currentVersion = version
logger.version = version
}
}
function setLogLevel (options) {
const logLevel = options.debug
? 'debug'
: options.verbose
? 'verbose'
: options.quiet
? 'error'
: options.logLevel
if (!logLevel) return
logger.setLevel(logLevel)
// Only log which level it's setting if it's not set to quiet mode
if (!options.quiet || (options.quiet && logLevel !== 'error')) {
logger.debug(`Setting log level to ${logLevel}`)
}
}
function setLogName (options) {
const logName = options.logName
if (!logName) return
logger.setName(logName)
}
function setLogVersion (options) {
const logVersion = options.logVersion
if (!logVersion) return
logger.setVersion(logVersion)
}
module.exports = {
logger,
getColor,
setLogLevel,
setLogName,
setLogVersion,
levels
}