@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 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.65.3...main)
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.65.3",
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})`)
@@ -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
- beforeOpsKeypair: () => {
58
- if (spinner) spinner.start('retrieving')
59
- },
60
- afterOpsKeypair: () => {
61
- if (spinner) spinner.start('injecting')
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
 
@@ -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', 'inherit']
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
- if (options.beforeOpsKeypair) await options.beforeOpsKeypair()
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 (options.beforeOpsKeypair || options.afterOpsKeypair) {
9
- kp = await new Ops().keypair(existingPublicKey, { noSpinner: true })
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 (options.afterOpsKeypair) await options.afterOpsKeypair()
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 kp = await opsKeypair()
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
- beforeOpsKeypair: opts.beforeOpsKeypair,
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
- // if noCreate is on then detectEncoding will throw and we'll halt the calls
82
- // but if noCreate is false then create the file if it doesn't exist
83
- if (!(await fsx.exists(filepath)) && !this.noCreate) {
84
- await fsx.writeFileX(filepath, SAMPLE_ENV_KIT)
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, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
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({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, noOps: this.noOps })
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
@@ -24,8 +24,7 @@ class Run {
24
24
  this.envKeysFilepath = envKeysFilepath
25
25
  this.noOps = noOps
26
26
  this.noSpinner = options.noSpinner
27
- this.beforeOpsKeypair = options.beforeOpsKeypair
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
- beforeOpsKeypair: this.beforeOpsKeypair,
193
- afterOpsKeypair: this.afterOpsKeypair
191
+ keypairHooks: this.keypairHooks
194
192
  })
195
193
 
196
194
  const {