@dotenvx/dotenvx 1.65.3 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/package.json +2 -1
- package/src/cli/actions/doctor.js +22 -0
- package/src/cli/actions/encrypt.js +58 -3
- package/src/cli/actions/run.js +7 -5
- package/src/cli/dotenvx.js +9 -0
- package/src/lib/extensions/ops.js +16 -3
- package/src/lib/helpers/cryptography/opsKeypair.js +10 -4
- package/src/lib/helpers/cryptography/provision.js +9 -2
- package/src/lib/helpers/keyResolution/keyValues.js +1 -2
- package/src/lib/helpers/prompts.js +43 -0
- package/src/lib/main.d.ts +17 -0
- package/src/lib/main.js +6 -0
- package/src/lib/services/doctor.js +93 -0
- package/src/lib/services/encrypt.js +27 -9
- package/src/lib/services/run.js +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.67.0...main)
|
|
6
|
+
|
|
7
|
+
## [1.67.0](https://github.com/dotenvx/dotenvx/compare/v1.66.0...v1.67.0) (2026-05-21)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add prompt for local storage vs armored storage ([#819](https://github.com/dotenvx/dotenvx/pull/819))
|
|
12
|
+
|
|
13
|
+
## [1.66.0](https://github.com/dotenvx/dotenvx/compare/v1.65.3...v1.66.0) (2026-05-13)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* Add `dotenvx doctor` ([#815](https://github.com/dotenvx/dotenvx/pull/815))
|
|
6
18
|
|
|
7
19
|
## [1.65.3](https://github.com/dotenvx/dotenvx/compare/v1.65.2...v1.65.3) (2026-05-13)
|
|
8
20
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.67.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "secrets for agents–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"commander": "^11.1.0",
|
|
47
47
|
"dotenv": "^17.2.1",
|
|
48
48
|
"eciesjs": "^0.4.10",
|
|
49
|
+
"enquirer": "^2.4.1",
|
|
49
50
|
"execa": "^5.1.1",
|
|
50
51
|
"fdir": "^6.2.0",
|
|
51
52
|
"ignore": "^5.3.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { logger } = require('./../../shared/logger')
|
|
2
|
+
const main = require('./../../lib/main')
|
|
3
|
+
|
|
4
|
+
function doctor (directory = '.') {
|
|
5
|
+
logger.debug(`directory: ${directory}`)
|
|
6
|
+
|
|
7
|
+
const findings = main.doctor(directory)
|
|
8
|
+
logger.debug(`findings: ${JSON.stringify(findings)}`)
|
|
9
|
+
|
|
10
|
+
if (findings.length === 0) {
|
|
11
|
+
logger.info('no dotenv loaders found')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
logger.warn(`found ${findings.length} possible dotenv loader${findings.length === 1 ? '' : 's'}`)
|
|
16
|
+
|
|
17
|
+
for (const finding of findings) {
|
|
18
|
+
logger.info(`│ ${finding.filepath}:${finding.line}: ${finding.code}`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = doctor
|
|
@@ -6,8 +6,63 @@ const Encrypt = require('./../../lib/services/encrypt')
|
|
|
6
6
|
const catchAndLog = require('../../lib/helpers/catchAndLog')
|
|
7
7
|
const localDisplayPath = require('../../lib/helpers/localDisplayPath')
|
|
8
8
|
const createSpinner = require('../../lib/helpers/createSpinner')
|
|
9
|
+
const prompts = require('../../lib/helpers/prompts')
|
|
9
10
|
const Session = require('../../db/session')
|
|
10
11
|
|
|
12
|
+
function keypairSpinnerHooks (spinner) {
|
|
13
|
+
let stoppedForOps = false
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
onStderr: () => {
|
|
17
|
+
if (spinner && !stoppedForOps) {
|
|
18
|
+
spinner.stop()
|
|
19
|
+
stoppedForOps = true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
after: () => {
|
|
23
|
+
if (spinner && stoppedForOps) {
|
|
24
|
+
spinner.start('encrypting')
|
|
25
|
+
stoppedForOps = false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function keyStorageSelector (spinner) {
|
|
32
|
+
let selected
|
|
33
|
+
|
|
34
|
+
return async function selectKeyStorage () {
|
|
35
|
+
if (selected) return selected
|
|
36
|
+
|
|
37
|
+
if (spinner) spinner.stop()
|
|
38
|
+
selected = await prompts.select({
|
|
39
|
+
message: 'Select key storage',
|
|
40
|
+
choices: [
|
|
41
|
+
{ name: 'Local (.env.keys)', value: 'local' },
|
|
42
|
+
{ name: 'Armored ⛨', value: 'armored' }
|
|
43
|
+
]
|
|
44
|
+
}, {
|
|
45
|
+
input: process.stdin,
|
|
46
|
+
output: process.stderr
|
|
47
|
+
})
|
|
48
|
+
if (spinner) spinner.start('encrypting')
|
|
49
|
+
|
|
50
|
+
return selected
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function encryptOptions (spinner, noOps) {
|
|
55
|
+
const options = {
|
|
56
|
+
keypairHooks: keypairSpinnerHooks(spinner)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!noOps) {
|
|
60
|
+
options.selectKeyStorage = keyStorageSelector(spinner)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return options
|
|
64
|
+
}
|
|
65
|
+
|
|
11
66
|
async function encrypt () {
|
|
12
67
|
const options = this.opts()
|
|
13
68
|
const spinner = await createSpinner({ ...options, text: 'encrypting' })
|
|
@@ -16,14 +71,14 @@ async function encrypt () {
|
|
|
16
71
|
|
|
17
72
|
const sesh = new Session()
|
|
18
73
|
const envs = this.envs
|
|
19
|
-
const noOps = options.ops === false || (await sesh.noOps())
|
|
74
|
+
const noOps = options.ops === false || (!options.token && (await sesh.noOps()))
|
|
20
75
|
const noCreate = options.create === false
|
|
21
76
|
|
|
22
77
|
// stdout - should not have a try so that exit codes can surface to stdout
|
|
23
78
|
if (options.stdout) {
|
|
24
79
|
const {
|
|
25
80
|
processedEnvs
|
|
26
|
-
} = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noOps, noCreate).run()
|
|
81
|
+
} = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noOps, noCreate, options.token, encryptOptions(spinner, noOps)).run()
|
|
27
82
|
if (spinner) spinner.stop()
|
|
28
83
|
for (const processedEnv of processedEnvs) {
|
|
29
84
|
console.log(processedEnv.envSrc)
|
|
@@ -35,7 +90,7 @@ async function encrypt () {
|
|
|
35
90
|
processedEnvs,
|
|
36
91
|
changedFilepaths,
|
|
37
92
|
unchangedFilepaths
|
|
38
|
-
} = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noOps, noCreate).run()
|
|
93
|
+
} = await new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, noOps, noCreate, options.token, encryptOptions(spinner, noOps)).run()
|
|
39
94
|
|
|
40
95
|
for (const processedEnv of processedEnvs) {
|
|
41
96
|
logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
|
package/src/cli/actions/run.js
CHANGED
|
@@ -54,11 +54,13 @@ async function run () {
|
|
|
54
54
|
readableFilepaths,
|
|
55
55
|
uniqueInjectedKeys
|
|
56
56
|
} = await new Run(envs, options.overload, process.env, options.envKeysFile, noOps, {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
keypairHooks: {
|
|
58
|
+
before: () => {
|
|
59
|
+
if (spinner) spinner.start('retrieving')
|
|
60
|
+
},
|
|
61
|
+
after: () => {
|
|
62
|
+
if (spinner) spinner.start('injecting')
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
}).run()
|
|
64
66
|
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -126,6 +126,7 @@ program.command('encrypt')
|
|
|
126
126
|
.option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
|
|
127
127
|
.option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
|
|
128
128
|
.option('--stdout', 'send to stdout')
|
|
129
|
+
.option('--token <token>', 'set Ops token')
|
|
129
130
|
.option('--no-create', 'do not create .env file(s) when missing')
|
|
130
131
|
.option('--no-ops', 'disable dotenvx-ops features')
|
|
131
132
|
.action(function (...args) {
|
|
@@ -186,6 +187,14 @@ program.command('ls')
|
|
|
186
187
|
return require('./actions/ls').apply(this, args)
|
|
187
188
|
})
|
|
188
189
|
|
|
190
|
+
// dotenvx doctor
|
|
191
|
+
program.command('doctor', { hidden: true })
|
|
192
|
+
.description('scan for dotenv loaders')
|
|
193
|
+
.argument('[directory]', 'directory to scan', '.')
|
|
194
|
+
.action(function (...args) {
|
|
195
|
+
return require('./actions/doctor').apply(this, args)
|
|
196
|
+
})
|
|
197
|
+
|
|
189
198
|
// dotenvx login
|
|
190
199
|
program.command('login')
|
|
191
200
|
.description('log in to unlock ⛨ ARMORED KEYS ✦ BETA')
|
|
@@ -40,10 +40,13 @@ class Ops {
|
|
|
40
40
|
|
|
41
41
|
const args = ['keypair']
|
|
42
42
|
if (options.noSpinner) args.push('--no-spinner')
|
|
43
|
+
if (options.token) args.push('--token', options.token)
|
|
43
44
|
if (publicKey) args.push(publicKey)
|
|
44
45
|
|
|
45
46
|
try {
|
|
46
|
-
return JSON.parse(await this._execInteractive(binary, args
|
|
47
|
+
return JSON.parse(await this._execInteractive(binary, args, {
|
|
48
|
+
onStderr: options.onStderr
|
|
49
|
+
}))
|
|
47
50
|
} catch (_e) {
|
|
48
51
|
return {}
|
|
49
52
|
}
|
|
@@ -57,6 +60,7 @@ class Ops {
|
|
|
57
60
|
|
|
58
61
|
const args = ['keypair']
|
|
59
62
|
if (options.noSpinner) args.push('--no-spinner')
|
|
63
|
+
if (options.token) args.push('--token', options.token)
|
|
60
64
|
if (publicKey) args.push(publicKey)
|
|
61
65
|
|
|
62
66
|
try {
|
|
@@ -106,17 +110,26 @@ class Ops {
|
|
|
106
110
|
return childProcess.execFileSync(binary, args).toString().trim()
|
|
107
111
|
}
|
|
108
112
|
|
|
109
|
-
_execInteractive (binary, args) {
|
|
113
|
+
_execInteractive (binary, args, options = {}) {
|
|
110
114
|
return new Promise((resolve, reject) => {
|
|
111
115
|
const spawnOptions = {
|
|
112
|
-
stdio: ['inherit', 'pipe', '
|
|
116
|
+
stdio: ['inherit', 'pipe', 'pipe']
|
|
113
117
|
}
|
|
114
118
|
const subprocess = childProcess.spawn(binary, args, spawnOptions)
|
|
115
119
|
let stdout = ''
|
|
120
|
+
let sawStderr = false
|
|
116
121
|
|
|
117
122
|
subprocess.stdout.on('data', (data) => {
|
|
118
123
|
stdout += data.toString()
|
|
119
124
|
})
|
|
125
|
+
subprocess.stderr.on('data', (data) => {
|
|
126
|
+
if (!sawStderr) {
|
|
127
|
+
sawStderr = true
|
|
128
|
+
if (options.onStderr) options.onStderr()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
process.stderr.write(data)
|
|
132
|
+
})
|
|
120
133
|
subprocess.on('error', reject)
|
|
121
134
|
subprocess.on('close', (code) => {
|
|
122
135
|
if (code !== 0) {
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
const Ops = require('../../extensions/ops')
|
|
2
2
|
|
|
3
3
|
async function opsKeypair (existingPublicKey, options = {}) {
|
|
4
|
-
|
|
4
|
+
const hooks = options.hooks || {}
|
|
5
|
+
if (hooks.before) await hooks.before()
|
|
6
|
+
|
|
7
|
+
const keypairOptions = {}
|
|
8
|
+
if (options.token) keypairOptions.token = options.token
|
|
9
|
+
if (hooks.onStderr) keypairOptions.onStderr = hooks.onStderr
|
|
10
|
+
if (hooks.before || hooks.onStderr || hooks.after) keypairOptions.noSpinner = true
|
|
5
11
|
|
|
6
12
|
let kp
|
|
7
13
|
try {
|
|
8
|
-
if (
|
|
9
|
-
kp = await new Ops().keypair(existingPublicKey,
|
|
14
|
+
if (Object.keys(keypairOptions).length > 0) {
|
|
15
|
+
kp = await new Ops().keypair(existingPublicKey, keypairOptions)
|
|
10
16
|
} else {
|
|
11
17
|
kp = await new Ops().keypair(existingPublicKey)
|
|
12
18
|
}
|
|
13
19
|
} finally {
|
|
14
|
-
if (
|
|
20
|
+
if (hooks.after) await hooks.after()
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
const publicKey = kp.public_key
|
|
@@ -4,8 +4,12 @@ const opsKeypair = require('./opsKeypair')
|
|
|
4
4
|
const localKeypair = require('./localKeypair')
|
|
5
5
|
const { keyNames } = require('../keyResolution')
|
|
6
6
|
|
|
7
|
-
async function provision ({ envSrc, envFilepath, keysFilepath, noOps }) {
|
|
7
|
+
async function provision ({ envSrc, envFilepath, keysFilepath, noOps, token, keypairHooks, selectKeyStorage }) {
|
|
8
8
|
noOps = noOps !== false
|
|
9
|
+
if (!noOps && selectKeyStorage) {
|
|
10
|
+
noOps = await selectKeyStorage() !== 'armored'
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
const { publicKeyName, privateKeyName } = keyNames(envFilepath)
|
|
10
14
|
|
|
11
15
|
let publicKey
|
|
@@ -20,7 +24,10 @@ async function provision ({ envSrc, envFilepath, keysFilepath, noOps }) {
|
|
|
20
24
|
publicKey = kp.publicKey
|
|
21
25
|
privateKey = kp.privateKey
|
|
22
26
|
} else {
|
|
23
|
-
const
|
|
27
|
+
const keypairOptions = {}
|
|
28
|
+
if (token) keypairOptions.token = token
|
|
29
|
+
if (keypairHooks) keypairOptions.hooks = keypairHooks
|
|
30
|
+
const kp = await opsKeypair(undefined, keypairOptions)
|
|
24
31
|
publicKey = kp.publicKey
|
|
25
32
|
privateKey = kp.privateKey
|
|
26
33
|
}
|
|
@@ -73,8 +73,7 @@ async function keyValues (filepath, opts = {}) {
|
|
|
73
73
|
// ops
|
|
74
74
|
if (!noOps && !privateKey && publicKey && publicKey.length > 0) {
|
|
75
75
|
const kp = await opsKeypair(publicKey, {
|
|
76
|
-
|
|
77
|
-
afterOpsKeypair: opts.afterOpsKeypair
|
|
76
|
+
hooks: opts.keypairHooks
|
|
78
77
|
})
|
|
79
78
|
privateKey = kp.privateKey
|
|
80
79
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const Enquirer = require('enquirer')
|
|
2
|
+
|
|
3
|
+
const enquirer = new Enquirer()
|
|
4
|
+
|
|
5
|
+
function choicesForSelect (choices) {
|
|
6
|
+
return choices.map(choice => {
|
|
7
|
+
if (typeof choice === 'string') return choice
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
name: choice.value,
|
|
11
|
+
message: choice.name || choice.value
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function enquirerOptions (context = {}) {
|
|
17
|
+
const options = {
|
|
18
|
+
// Enquirer names the render stream stdout; use stderr so stdout stays machine-readable.
|
|
19
|
+
stdout: context.output || process.stderr
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (context.input) {
|
|
23
|
+
options.stdin = context.input
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return options
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function select ({ message, choices }, context) {
|
|
30
|
+
const answer = await enquirer.prompt({
|
|
31
|
+
type: 'select',
|
|
32
|
+
name: 'value',
|
|
33
|
+
message,
|
|
34
|
+
choices: choicesForSelect(choices),
|
|
35
|
+
...enquirerOptions(context)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return answer.value
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
select
|
|
43
|
+
}
|
package/src/lib/main.d.ts
CHANGED
|
@@ -363,6 +363,23 @@ export function ls(
|
|
|
363
363
|
excludeEnvFile: string | string[]
|
|
364
364
|
): string[];
|
|
365
365
|
|
|
366
|
+
export type DoctorFinding = {
|
|
367
|
+
lang: string;
|
|
368
|
+
filepath: string;
|
|
369
|
+
line: number;
|
|
370
|
+
code: string;
|
|
371
|
+
msg: string;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Scan code for dotenv loaders that can conflict with dotenvx.
|
|
376
|
+
*
|
|
377
|
+
* @param directory - directory to scan
|
|
378
|
+
*/
|
|
379
|
+
export function doctor(
|
|
380
|
+
directory: string
|
|
381
|
+
): DoctorFinding[];
|
|
382
|
+
|
|
366
383
|
export type GenExampleOutput = {
|
|
367
384
|
envExampleFile: string;
|
|
368
385
|
envFile: string | string[];
|
package/src/lib/main.js
CHANGED
|
@@ -7,6 +7,7 @@ const { getColor, bold } = require('./../shared/colors')
|
|
|
7
7
|
|
|
8
8
|
// services
|
|
9
9
|
const Ls = require('./services/ls')
|
|
10
|
+
const Doctor = require('./services/doctor')
|
|
10
11
|
const Run = require('./services/run')
|
|
11
12
|
const Sets = require('./services/sets')
|
|
12
13
|
const Get = require('./services/get')
|
|
@@ -298,6 +299,10 @@ const ls = function (directory, envFile, excludeEnvFile) {
|
|
|
298
299
|
return new Ls(directory, envFile, excludeEnvFile).run()
|
|
299
300
|
}
|
|
300
301
|
|
|
302
|
+
const doctor = function (directory) {
|
|
303
|
+
return new Doctor(directory).run()
|
|
304
|
+
}
|
|
305
|
+
|
|
301
306
|
/** @type {import('./main').genexample} */
|
|
302
307
|
const genexample = function (directory, envFile) {
|
|
303
308
|
return new Genexample(directory, envFile).run()
|
|
@@ -326,6 +331,7 @@ module.exports = {
|
|
|
326
331
|
set,
|
|
327
332
|
get,
|
|
328
333
|
ls,
|
|
334
|
+
doctor,
|
|
329
335
|
keypair,
|
|
330
336
|
genexample,
|
|
331
337
|
// expose for libs depending on @dotenvx/dotenvx - like dotenvx-ops
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const { fdir: Fdir } = require('fdir')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const picomatch = require('picomatch')
|
|
4
|
+
const fsx = require('./../helpers/fsx')
|
|
5
|
+
|
|
6
|
+
const patterns = [
|
|
7
|
+
{ lang: 'Python', file: /\.py$/, match: /load_dotenv\s*\([^)]*\)/, msg: 'found python-dotenv load call' },
|
|
8
|
+
{ lang: 'Python', file: /\.py$/, match: /dotenv_values\s*\([^)]*\)/, msg: 'found python-dotenv values call' },
|
|
9
|
+
|
|
10
|
+
{ lang: 'Node', file: /\.(js|cjs|mjs|ts)$/, match: /require\(['"]dotenv['"]\)\.config\s*\([^)]*\)/, msg: 'found dotenv config require call' },
|
|
11
|
+
{ lang: 'Node', file: /\.(js|cjs|mjs|ts)$/, match: /import\s+['"]dotenv\/config['"]/, msg: 'found dotenv/config import' },
|
|
12
|
+
{ lang: 'Node', file: /\.(js|cjs|mjs|ts)$/, match: /dotenv\.config\s*\([^)]*\)/, msg: 'found dotenv config call' },
|
|
13
|
+
|
|
14
|
+
{ lang: 'Ruby', file: /\.rb$/, match: /Dotenv\.(load|overload)\s*\([^)]*\)/, msg: 'found ruby dotenv load call' },
|
|
15
|
+
{ lang: 'Go', file: /\.go$/, match: /godotenv\.(Load|Overload)\s*\([^)]*\)/, msg: 'found godotenv load call' },
|
|
16
|
+
{ lang: 'PHP', file: /\.php$/, match: /Dotenv\\Dotenv::create\w*\s*\([^)]*\)/, msg: 'found vlucas/phpdotenv loader' },
|
|
17
|
+
|
|
18
|
+
{ lang: 'Rust', file: /\.rs$/, match: /dotenvy?::dotenv\s*\([^)]*\)/, msg: 'found Rust dotenv load call' },
|
|
19
|
+
{ lang: 'Java', file: /\.java$/, match: /Dotenv\.(load|configure)\s*\([^)]*\)/, msg: 'found java-dotenv load call' },
|
|
20
|
+
{ lang: 'Kotlin', file: /\.kt$/, match: /Dotenv\.(load|configure)\s*\([^)]*\)/, msg: 'found java-dotenv load call' },
|
|
21
|
+
{ lang: '.NET', file: /\.(cs|fs|vb)$/, match: /(?:DotNetEnv\.)?Env\.Load\s*\([^)]*\)/, msg: 'found DotNetEnv load call' }
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
class Doctor {
|
|
25
|
+
constructor (directory = './') {
|
|
26
|
+
this.cwd = path.resolve(directory)
|
|
27
|
+
this.ignore = [
|
|
28
|
+
'**/node_modules/**',
|
|
29
|
+
'**/.git/**',
|
|
30
|
+
'**/vendor/**',
|
|
31
|
+
'**/dist/**',
|
|
32
|
+
'**/build/**',
|
|
33
|
+
'**/coverage/**',
|
|
34
|
+
'**/.next/**'
|
|
35
|
+
]
|
|
36
|
+
this.exclude = picomatch(this.ignore)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
run () {
|
|
40
|
+
const findings = []
|
|
41
|
+
|
|
42
|
+
for (const filepath of this._filepaths()) {
|
|
43
|
+
findings.push(...this._scanFile(filepath))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return findings
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_filepaths () {
|
|
50
|
+
return new Fdir()
|
|
51
|
+
.withRelativePaths()
|
|
52
|
+
.exclude((dir, filepath) => this._ignored(filepath))
|
|
53
|
+
.filter((filepath) => this._patternsFor(filepath).length > 0)
|
|
54
|
+
.crawl(this.cwd)
|
|
55
|
+
.sync()
|
|
56
|
+
.sort()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_scanFile (relativeFilepath) {
|
|
60
|
+
const src = fsx.readFileXSync(path.join(this.cwd, relativeFilepath))
|
|
61
|
+
const lines = src.split(/\r?\n/)
|
|
62
|
+
const findings = []
|
|
63
|
+
|
|
64
|
+
lines.forEach((line, index) => {
|
|
65
|
+
for (const pattern of this._patternsFor(relativeFilepath)) {
|
|
66
|
+
if (pattern.match.test(line)) {
|
|
67
|
+
const match = line.match(pattern.match)
|
|
68
|
+
findings.push({
|
|
69
|
+
lang: pattern.lang,
|
|
70
|
+
filepath: relativeFilepath,
|
|
71
|
+
line: index + 1,
|
|
72
|
+
code: match[0].trim(),
|
|
73
|
+
msg: pattern.msg
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return findings
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_patternsFor (filepath) {
|
|
83
|
+
return patterns.filter(pattern => pattern.file.test(filepath))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_ignored (filepath) {
|
|
87
|
+
return this.exclude(filepath)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Doctor.patterns = patterns
|
|
92
|
+
|
|
93
|
+
module.exports = Doctor
|
|
@@ -29,13 +29,16 @@ const detectEncoding = require('./../helpers/detectEncoding')
|
|
|
29
29
|
const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
|
|
30
30
|
|
|
31
31
|
class Encrypt {
|
|
32
|
-
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, noOps = false, noCreate = false) {
|
|
32
|
+
constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, noOps = false, noCreate = false, token = undefined, options = {}) {
|
|
33
33
|
this.envs = determine(envs, process.env)
|
|
34
34
|
this.key = key
|
|
35
35
|
this.excludeKey = excludeKey
|
|
36
36
|
this.envKeysFilepath = envKeysFilepath
|
|
37
37
|
this.noOps = noOps
|
|
38
38
|
this.noCreate = noCreate
|
|
39
|
+
this.token = token
|
|
40
|
+
this.keypairHooks = options.keypairHooks
|
|
41
|
+
this.selectKeyStorage = options.selectKeyStorage
|
|
39
42
|
|
|
40
43
|
this.processedEnvs = []
|
|
41
44
|
this.changedFilepaths = new Set()
|
|
@@ -78,14 +81,17 @@ class Encrypt {
|
|
|
78
81
|
row.envFilepath = envFilepath
|
|
79
82
|
|
|
80
83
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!
|
|
84
|
-
|
|
84
|
+
const fileExists = await fsx.exists(filepath)
|
|
85
|
+
let envSrc
|
|
86
|
+
if (!fileExists && !this.noCreate) {
|
|
87
|
+
envSrc = SAMPLE_ENV_KIT
|
|
85
88
|
fileCreated = true
|
|
89
|
+
row.kitCreated = 'sample'
|
|
90
|
+
row.changed = true
|
|
91
|
+
} else {
|
|
92
|
+
const encoding = await detectEncoding(filepath)
|
|
93
|
+
envSrc = await fsx.readFileX(filepath, { encoding })
|
|
86
94
|
}
|
|
87
|
-
const encoding = await detectEncoding(filepath)
|
|
88
|
-
let envSrc = await fsx.readFileX(filepath, { encoding })
|
|
89
95
|
if (envSrc.trim().length === 0) {
|
|
90
96
|
envSrc = SAMPLE_ENV_KIT
|
|
91
97
|
row.kitCreated = 'sample'
|
|
@@ -97,11 +103,23 @@ class Encrypt {
|
|
|
97
103
|
let privateKey
|
|
98
104
|
|
|
99
105
|
const { publicKeyName, privateKeyName } = keyNames(envFilepath)
|
|
100
|
-
const { publicKeyValue, privateKeyValue } = await keyValues(envFilepath, {
|
|
106
|
+
const { publicKeyValue, privateKeyValue } = await keyValues(envFilepath, {
|
|
107
|
+
keysFilepath: this.envKeysFilepath,
|
|
108
|
+
noOps: this.noOps,
|
|
109
|
+
keypairHooks: this.keypairHooks
|
|
110
|
+
})
|
|
101
111
|
|
|
102
112
|
// first pass - provision
|
|
103
113
|
if (!privateKeyValue && !publicKeyValue) {
|
|
104
|
-
const prov = await provision({
|
|
114
|
+
const prov = await provision({
|
|
115
|
+
envSrc,
|
|
116
|
+
envFilepath,
|
|
117
|
+
keysFilepath: this.envKeysFilepath,
|
|
118
|
+
noOps: this.noOps,
|
|
119
|
+
token: this.token,
|
|
120
|
+
keypairHooks: this.keypairHooks,
|
|
121
|
+
selectKeyStorage: this.selectKeyStorage
|
|
122
|
+
})
|
|
105
123
|
envSrc = prov.envSrc
|
|
106
124
|
publicKey = prov.publicKey
|
|
107
125
|
privateKey = prov.privateKey
|
package/src/lib/services/run.js
CHANGED
|
@@ -24,8 +24,7 @@ class Run {
|
|
|
24
24
|
this.envKeysFilepath = envKeysFilepath
|
|
25
25
|
this.noOps = noOps
|
|
26
26
|
this.noSpinner = options.noSpinner
|
|
27
|
-
this.
|
|
28
|
-
this.afterOpsKeypair = options.afterOpsKeypair
|
|
27
|
+
this.keypairHooks = options.keypairHooks
|
|
29
28
|
|
|
30
29
|
this.processedEnvs = []
|
|
31
30
|
this.readableFilepaths = new Set()
|
|
@@ -189,8 +188,7 @@ class Run {
|
|
|
189
188
|
const { privateKeyValue } = await keyValues(filepath, {
|
|
190
189
|
keysFilepath: this.envKeysFilepath,
|
|
191
190
|
noOps: this.noOps,
|
|
192
|
-
|
|
193
|
-
afterOpsKeypair: this.afterOpsKeypair
|
|
191
|
+
keypairHooks: this.keypairHooks
|
|
194
192
|
})
|
|
195
193
|
|
|
196
194
|
const {
|