@dotenvx/dotenvx 1.65.2 → 1.66.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 +1 -1
- package/src/cli/actions/doctor.js +22 -0
- package/src/cli/actions/run.js +1 -1
- package/src/cli/dotenvx.js +8 -0
- package/src/lib/extensions/ops.js +7 -4
- package/src/lib/helpers/cryptography/opsKeypair.js +5 -1
- package/src/lib/helpers/cryptography/opsKeypairSync.js +3 -2
- package/src/lib/helpers/keyResolution/keyValuesSync.js +6 -1
- package/src/lib/main.d.ts +25 -0
- package/src/lib/main.js +9 -1
- package/src/lib/services/doctor.js +93 -0
- package/src/lib/services/run.js +6 -1
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.66.0...main)
|
|
6
|
+
|
|
7
|
+
## [1.66.0](https://github.com/dotenvx/dotenvx/compare/v1.65.3...v1.66.0) (2026-05-13)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add `dotenvx doctor` ([#815](https://github.com/dotenvx/dotenvx/pull/815))
|
|
12
|
+
|
|
13
|
+
## [1.65.3](https://github.com/dotenvx/dotenvx/compare/v1.65.2...v1.65.3) (2026-05-13)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* Improve spinner message blinking with simpler `--no-spinner` flag passed to ops ([#814](https://github.com/dotenvx/dotenvx/pull/814))
|
|
6
18
|
|
|
7
19
|
## [1.65.2](https://github.com/dotenvx/dotenvx/compare/v1.65.1...v1.65.2) (2026-05-13)
|
|
8
20
|
|
package/package.json
CHANGED
|
@@ -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
|
package/src/cli/actions/run.js
CHANGED
|
@@ -55,7 +55,7 @@ async function run () {
|
|
|
55
55
|
uniqueInjectedKeys
|
|
56
56
|
} = await new Run(envs, options.overload, process.env, options.envKeysFile, noOps, {
|
|
57
57
|
beforeOpsKeypair: () => {
|
|
58
|
-
if (spinner) spinner.
|
|
58
|
+
if (spinner) spinner.start('retrieving')
|
|
59
59
|
},
|
|
60
60
|
afterOpsKeypair: () => {
|
|
61
61
|
if (spinner) spinner.start('injecting')
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -186,6 +186,14 @@ program.command('ls')
|
|
|
186
186
|
return require('./actions/ls').apply(this, args)
|
|
187
187
|
})
|
|
188
188
|
|
|
189
|
+
// dotenvx doctor
|
|
190
|
+
program.command('doctor', { hidden: true })
|
|
191
|
+
.description('scan for dotenv loaders')
|
|
192
|
+
.argument('[directory]', 'directory to scan', '.')
|
|
193
|
+
.action(function (...args) {
|
|
194
|
+
return require('./actions/doctor').apply(this, args)
|
|
195
|
+
})
|
|
196
|
+
|
|
189
197
|
// dotenvx login
|
|
190
198
|
program.command('login')
|
|
191
199
|
.description('log in to unlock ⛨ ARMORED KEYS ✦ BETA')
|
|
@@ -32,13 +32,14 @@ class Ops {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async keypair (publicKey) {
|
|
35
|
+
async keypair (publicKey, options = {}) {
|
|
36
36
|
if (this._isForcedOff()) return {}
|
|
37
37
|
|
|
38
38
|
const binary = await this._resolveBinary()
|
|
39
39
|
if (!binary) return {}
|
|
40
40
|
|
|
41
41
|
const args = ['keypair']
|
|
42
|
+
if (options.noSpinner) args.push('--no-spinner')
|
|
42
43
|
if (publicKey) args.push(publicKey)
|
|
43
44
|
|
|
44
45
|
try {
|
|
@@ -48,13 +49,14 @@ class Ops {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
keypairSync (publicKey) {
|
|
52
|
+
keypairSync (publicKey, options = {}) {
|
|
52
53
|
if (this._isForcedOff()) return {}
|
|
53
54
|
|
|
54
55
|
const binary = this._resolveBinarySync()
|
|
55
56
|
if (!binary) return {}
|
|
56
57
|
|
|
57
58
|
const args = ['keypair']
|
|
59
|
+
if (options.noSpinner) args.push('--no-spinner')
|
|
58
60
|
if (publicKey) args.push(publicKey)
|
|
59
61
|
|
|
60
62
|
try {
|
|
@@ -106,9 +108,10 @@ class Ops {
|
|
|
106
108
|
|
|
107
109
|
_execInteractive (binary, args) {
|
|
108
110
|
return new Promise((resolve, reject) => {
|
|
109
|
-
const
|
|
111
|
+
const spawnOptions = {
|
|
110
112
|
stdio: ['inherit', 'pipe', 'inherit']
|
|
111
|
-
}
|
|
113
|
+
}
|
|
114
|
+
const subprocess = childProcess.spawn(binary, args, spawnOptions)
|
|
112
115
|
let stdout = ''
|
|
113
116
|
|
|
114
117
|
subprocess.stdout.on('data', (data) => {
|
|
@@ -5,7 +5,11 @@ async function opsKeypair (existingPublicKey, options = {}) {
|
|
|
5
5
|
|
|
6
6
|
let kp
|
|
7
7
|
try {
|
|
8
|
-
|
|
8
|
+
if (options.beforeOpsKeypair || options.afterOpsKeypair) {
|
|
9
|
+
kp = await new Ops().keypair(existingPublicKey, { noSpinner: true })
|
|
10
|
+
} else {
|
|
11
|
+
kp = await new Ops().keypair(existingPublicKey)
|
|
12
|
+
}
|
|
9
13
|
} finally {
|
|
10
14
|
if (options.afterOpsKeypair) await options.afterOpsKeypair()
|
|
11
15
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const Ops = require('../../extensions/ops')
|
|
2
2
|
|
|
3
|
-
function opsKeypairSync (existingPublicKey) {
|
|
4
|
-
const
|
|
3
|
+
function opsKeypairSync (existingPublicKey, options = {}) {
|
|
4
|
+
const ops = new Ops()
|
|
5
|
+
const kp = Object.keys(options).length > 0 ? ops.keypairSync(existingPublicKey, options) : ops.keypairSync(existingPublicKey)
|
|
5
6
|
const publicKey = kp.public_key
|
|
6
7
|
const privateKey = kp.private_key
|
|
7
8
|
|
|
@@ -72,7 +72,12 @@ function keyValuesSync (filepath, opts = {}) {
|
|
|
72
72
|
|
|
73
73
|
// ops
|
|
74
74
|
if (!noOps && !privateKey && publicKey && publicKey.length > 0) {
|
|
75
|
-
const
|
|
75
|
+
const opsOptions = {}
|
|
76
|
+
if (opts.noSpinner) {
|
|
77
|
+
opsOptions.noSpinner = true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const kp = Object.keys(opsOptions).length > 0 ? opsKeypairSync(publicKey, opsOptions) : opsKeypairSync(publicKey)
|
|
76
81
|
privateKey = kp.privateKey
|
|
77
82
|
}
|
|
78
83
|
|
package/src/lib/main.d.ts
CHANGED
|
@@ -146,6 +146,14 @@ export interface DotenvConfigOptions {
|
|
|
146
146
|
|
|
147
147
|
quiet?: boolean;
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Disable spinner output from child Dotenvx Ops commands.
|
|
151
|
+
*
|
|
152
|
+
* @default false
|
|
153
|
+
* @example require('@dotenvx/dotenvx').config({ noSpinner: true })
|
|
154
|
+
*/
|
|
155
|
+
noSpinner?: boolean;
|
|
156
|
+
|
|
149
157
|
logLevel?:
|
|
150
158
|
| 'error'
|
|
151
159
|
| 'warn'
|
|
@@ -355,6 +363,23 @@ export function ls(
|
|
|
355
363
|
excludeEnvFile: string | string[]
|
|
356
364
|
): string[];
|
|
357
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
|
+
|
|
358
383
|
export type GenExampleOutput = {
|
|
359
384
|
envExampleFile: string;
|
|
360
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')
|
|
@@ -59,7 +60,9 @@ const config = function (options = {}) {
|
|
|
59
60
|
processedEnvs,
|
|
60
61
|
readableFilepaths,
|
|
61
62
|
uniqueInjectedKeys
|
|
62
|
-
} = new Run(envs, overload, processEnv, envKeysFile, noOps
|
|
63
|
+
} = new Run(envs, overload, processEnv, envKeysFile, noOps, {
|
|
64
|
+
noSpinner: options.noSpinner
|
|
65
|
+
}).runSync()
|
|
63
66
|
|
|
64
67
|
let lastError
|
|
65
68
|
/** @type {Record<string, string>} */
|
|
@@ -296,6 +299,10 @@ const ls = function (directory, envFile, excludeEnvFile) {
|
|
|
296
299
|
return new Ls(directory, envFile, excludeEnvFile).run()
|
|
297
300
|
}
|
|
298
301
|
|
|
302
|
+
const doctor = function (directory) {
|
|
303
|
+
return new Doctor(directory).run()
|
|
304
|
+
}
|
|
305
|
+
|
|
299
306
|
/** @type {import('./main').genexample} */
|
|
300
307
|
const genexample = function (directory, envFile) {
|
|
301
308
|
return new Genexample(directory, envFile).run()
|
|
@@ -324,6 +331,7 @@ module.exports = {
|
|
|
324
331
|
set,
|
|
325
332
|
get,
|
|
326
333
|
ls,
|
|
334
|
+
doctor,
|
|
327
335
|
keypair,
|
|
328
336
|
genexample,
|
|
329
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
|
package/src/lib/services/run.js
CHANGED
|
@@ -23,6 +23,7 @@ class Run {
|
|
|
23
23
|
this.processEnv = processEnv
|
|
24
24
|
this.envKeysFilepath = envKeysFilepath
|
|
25
25
|
this.noOps = noOps
|
|
26
|
+
this.noSpinner = options.noSpinner
|
|
26
27
|
this.beforeOpsKeypair = options.beforeOpsKeypair
|
|
27
28
|
this.afterOpsKeypair = options.afterOpsKeypair
|
|
28
29
|
|
|
@@ -136,7 +137,11 @@ class Run {
|
|
|
136
137
|
this.readableFilepaths.add(envFilepath)
|
|
137
138
|
|
|
138
139
|
const { privateKeyName } = keyNames(filepath)
|
|
139
|
-
const { privateKeyValue } = keyValuesSync(filepath, {
|
|
140
|
+
const { privateKeyValue } = keyValuesSync(filepath, {
|
|
141
|
+
keysFilepath: this.envKeysFilepath,
|
|
142
|
+
noOps: this.noOps,
|
|
143
|
+
noSpinner: this.noSpinner
|
|
144
|
+
})
|
|
140
145
|
|
|
141
146
|
const {
|
|
142
147
|
parsed,
|