@dotenvx/dotenvx-ops 0.40.0 → 0.42.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-ops/compare/v0.40.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.42.0...main)
6
+
7
+ ## [0.42.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.41.0...v0.42.0) (2026-04-26)
8
+
9
+ ### Added
10
+
11
+ * Add `armor down` ([#59](https://github.com/dotenvx/dotenvx-ops/pull/59))
12
+
13
+ ## [0.41.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.40.0...v0.41.0) (2026-04-26)
14
+
15
+ ### Added
16
+
17
+ * Add `armor up` ([#58](https://github.com/dotenvx/dotenvx-ops/pull/58))
6
18
 
7
19
  ## [0.40.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.39.1...v0.40.0) (2026-04-24)
8
20
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.40.0",
2
+ "version": "0.42.0",
3
3
  "name": "@dotenvx/dotenvx-ops",
4
4
  "description": "Secrets for agents–from the creator of `dotenv` and `dotenvx`",
5
5
  "author": "@motdotla",
@@ -1,14 +1,32 @@
1
1
  const { logger } = require('@dotenvx/dotenvx')
2
-
3
2
  const Session = require('./../../../db/session')
3
+ const ArmorDown = require('./../../../lib/services/armorDown')
4
+ const createSpinner = require('../../../lib/helpers/createSpinner2')
5
+
6
+ async function down () {
7
+ const options = this.opts()
8
+ const spinner = await createSpinner({ ...options, text: 'dearmoring' })
9
+
10
+ logger.debug(`options: ${JSON.stringify(options)}`)
11
+
12
+ const sesh = new Session()
13
+ await sesh.notifyUpdate()
14
+ const hostname = options.hostname || sesh.hostname()
15
+ const token = options.token || sesh.token()
4
16
 
5
- function down () {
6
17
  try {
7
- const sesh = new Session()
8
- sesh.notifyUpdate()
18
+ const devicePublicKey = sesh.devicePublicKey()
19
+
20
+ const { changed, privateKeyName } = await new ArmorDown(hostname, token, devicePublicKey, options.envFile).run()
9
21
 
10
- logger.success('⛨ coming soon')
22
+ if (spinner) spinner.stop()
23
+ if (changed) {
24
+ logger.success(`◇ dearmored ${privateKeyName} (.env.keys)`)
25
+ } else {
26
+ logger.info(`○ no change ${privateKeyName} (.env.keys)`)
27
+ }
11
28
  } catch (error) {
29
+ if (spinner) spinner.stop()
12
30
  logger.error(error.message)
13
31
  process.exit(1)
14
32
  }
@@ -1,14 +1,32 @@
1
1
  const { logger } = require('@dotenvx/dotenvx')
2
-
3
2
  const Session = require('./../../../db/session')
3
+ const ArmorUp = require('./../../../lib/services/armorUp')
4
+ const createSpinner = require('../../../lib/helpers/createSpinner2')
5
+
6
+ async function up () {
7
+ const options = this.opts()
8
+ const spinner = await createSpinner({ ...options, text: 'armoring' })
9
+
10
+ logger.debug(`options: ${JSON.stringify(options)}`)
11
+
12
+ const sesh = new Session()
13
+ sesh.notifyUpdate()
14
+ const hostname = options.hostname || sesh.hostname()
15
+ const token = options.token || sesh.token()
4
16
 
5
- function up () {
6
17
  try {
7
- const sesh = new Session()
8
- sesh.notifyUpdate()
18
+ const devicePublicKey = sesh.devicePublicKey()
19
+
20
+ const { changed, privateKeyName } = await new ArmorUp(hostname, token, devicePublicKey, options.envFile).run()
9
21
 
10
- logger.success('⛨ coming soon')
22
+ if (spinner) spinner.stop()
23
+ if (changed) {
24
+ logger.success(`◈ armored ${privateKeyName} (armored ⛨)`)
25
+ } else {
26
+ logger.info(`○ no change ${privateKeyName} (armored ⛨)`)
27
+ }
11
28
  } catch (error) {
29
+ if (spinner) spinner.stop()
12
30
  logger.error(error.message)
13
31
  process.exit(1)
14
32
  }
@@ -16,14 +16,16 @@ armor
16
16
  const upAction = require('./../actions/armor/up')
17
17
  armor
18
18
  .command('up')
19
- .description('harden private key')
19
+ .description('armor private key')
20
+ .option('-f, --env-file <path>', 'path to your env file')
20
21
  .action(upAction)
21
22
 
22
23
  // dotenvx-ops armor down
23
24
  const downAction = require('./../actions/armor/down')
24
25
  armor
25
26
  .command('down')
26
- .description('soften private key')
27
+ .description('dearmor private key')
28
+ .option('-f, --env-file <path>', 'path to your env file')
27
29
  .action(downAction)
28
30
 
29
31
  // dotenvx-ops armor push
@@ -0,0 +1,45 @@
1
+ const { http } = require('../../lib/helpers/http')
2
+ const buildApiError = require('../../lib/helpers/buildApiError')
3
+ const packageJson = require('../../lib/helpers/packageJson')
4
+ const normalizeToken = require('../../lib/helpers/normalizeToken')
5
+
6
+ class PostArmorDown {
7
+ constructor (hostname, token, devicePublicKey, publicKey) {
8
+ this.hostname = hostname || 'https://ops.dotenvx.com'
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.publicKey = publicKey
12
+ }
13
+
14
+ async run () {
15
+ const token = normalizeToken(this.token)
16
+ const devicePublicKey = this.devicePublicKey
17
+ const publicKey = this.publicKey
18
+ const url = `${this.hostname}/api/armor/down`
19
+
20
+ const body = {
21
+ device_public_key: devicePublicKey,
22
+ cli_version: packageJson.version,
23
+ public_key: publicKey
24
+ }
25
+
26
+ const resp = await http(url, {
27
+ method: 'POST',
28
+ headers: {
29
+ Authorization: `Bearer ${token}`,
30
+ 'Content-Type': 'application/json'
31
+ },
32
+ body: JSON.stringify(body)
33
+ })
34
+
35
+ const json = await resp.body.json()
36
+
37
+ if (resp.statusCode >= 400) {
38
+ throw buildApiError(resp.statusCode, json)
39
+ }
40
+
41
+ return json
42
+ }
43
+ }
44
+
45
+ module.exports = PostArmorDown
@@ -0,0 +1,48 @@
1
+ const { http } = require('../../lib/helpers/http')
2
+ const buildApiError = require('../../lib/helpers/buildApiError')
3
+ const packageJson = require('../../lib/helpers/packageJson')
4
+ const normalizeToken = require('../../lib/helpers/normalizeToken')
5
+
6
+ class PostArmorUp {
7
+ constructor (hostname, token, devicePublicKey, publicKey, privateKey) {
8
+ this.hostname = hostname || 'https://ops.dotenvx.com'
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.publicKey = publicKey
12
+ this.privateKey = privateKey
13
+ }
14
+
15
+ async run () {
16
+ const token = normalizeToken(this.token)
17
+ const devicePublicKey = this.devicePublicKey
18
+ const publicKey = this.publicKey
19
+ const privateKey = this.privateKey
20
+ const url = `${this.hostname}/api/armor/up`
21
+
22
+ const body = {
23
+ device_public_key: devicePublicKey,
24
+ cli_version: packageJson.version,
25
+ public_key: publicKey,
26
+ private_key: privateKey
27
+ }
28
+
29
+ const resp = await http(url, {
30
+ method: 'POST',
31
+ headers: {
32
+ Authorization: `Bearer ${token}`,
33
+ 'Content-Type': 'application/json'
34
+ },
35
+ body: JSON.stringify(body)
36
+ })
37
+
38
+ const json = await resp.body.json()
39
+
40
+ if (resp.statusCode >= 400) {
41
+ throw buildApiError(resp.statusCode, json)
42
+ }
43
+
44
+ return json
45
+ }
46
+ }
47
+
48
+ module.exports = PostArmorUp
@@ -0,0 +1,50 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ function escapeForRegex (value) {
5
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
6
+ }
7
+
8
+ function removeEnvKey (key, keysFilepath = '.env.keys') {
9
+ const resolvedKeysFilepath = path.resolve(keysFilepath)
10
+
11
+ if (!fs.existsSync(resolvedKeysFilepath)) {
12
+ return {
13
+ changed: false,
14
+ key,
15
+ filepath: keysFilepath
16
+ }
17
+ }
18
+
19
+ const src = fs.readFileSync(resolvedKeysFilepath, 'utf8')
20
+ const eol = src.includes('\r\n') ? '\r\n' : '\n'
21
+ const keyPattern = new RegExp(`^\\s*(?:export\\s+)?${escapeForRegex(key)}\\s*=`)
22
+ const envKeyPattern = /^\s*(?:export\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=/
23
+ const lines = src.length > 0 ? src.split(/\r?\n/) : []
24
+
25
+ while (lines.length > 0 && lines[lines.length - 1] === '') {
26
+ lines.pop()
27
+ }
28
+
29
+ const nextLines = lines.filter((line) => !keyPattern.test(line))
30
+ const changed = nextLines.length !== lines.length
31
+
32
+ if (changed) {
33
+ const hasRemainingKeys = nextLines.some((line) => envKeyPattern.test(line))
34
+
35
+ if (hasRemainingKeys) {
36
+ const nextSrc = `${nextLines.join(eol)}${eol}`
37
+ fs.writeFileSync(resolvedKeysFilepath, nextSrc, 'utf8')
38
+ } else {
39
+ fs.rmSync(resolvedKeysFilepath, { force: true })
40
+ }
41
+ }
42
+
43
+ return {
44
+ changed,
45
+ key,
46
+ filepath: keysFilepath
47
+ }
48
+ }
49
+
50
+ module.exports = removeEnvKey
@@ -0,0 +1,39 @@
1
+ const dotenvx = require('@dotenvx/dotenvx')
2
+ const PostArmorDown = require('../api/postArmorDown')
3
+ const keyNamesForEnvFile = require('../helpers/keyNamesForEnvFile')
4
+ const upsertEnvKey = require('../helpers/upsertEnvKey')
5
+
6
+ class ArmorDown {
7
+ constructor (hostname, token, devicePublicKey, envFile = '.env') {
8
+ this.hostname = hostname
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.envFile = envFile
12
+ }
13
+
14
+ async run () {
15
+ const hostname = this.hostname
16
+ const token = this.token
17
+ const devicePublicKey = this.devicePublicKey
18
+ const envFile = this.envFile
19
+
20
+ const {
21
+ publicKeyName,
22
+ privateKeyName
23
+ } = keyNamesForEnvFile(envFile)
24
+
25
+ const publicKey = dotenvx.get(publicKeyName, { path: envFile, strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
26
+ const json = await new PostArmorDown(hostname, token, devicePublicKey, publicKey).run()
27
+
28
+ upsertEnvKey(privateKeyName, json.private_key)
29
+
30
+ return {
31
+ ...json,
32
+ changed: json.changed,
33
+ privateKeyName,
34
+ privateKeyValue: json.private_key
35
+ }
36
+ }
37
+ }
38
+
39
+ module.exports = ArmorDown
@@ -0,0 +1,39 @@
1
+ const dotenvx = require('@dotenvx/dotenvx')
2
+ const PostArmorUp = require('../api/postArmorUp')
3
+ const keyNamesForEnvFile = require('../helpers/keyNamesForEnvFile')
4
+ const removeEnvKey = require('../helpers/removeEnvKey')
5
+
6
+ class ArmorUp {
7
+ constructor (hostname, token, devicePublicKey, envFile = '.env') {
8
+ this.hostname = hostname
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.envFile = envFile
12
+ }
13
+
14
+ async run () {
15
+ const hostname = this.hostname
16
+ const token = this.token
17
+ const devicePublicKey = this.devicePublicKey
18
+ const envFile = this.envFile
19
+
20
+ const {
21
+ publicKeyName,
22
+ privateKeyName
23
+ } = keyNamesForEnvFile(envFile)
24
+
25
+ const publicKey = dotenvx.get(publicKeyName, { path: envFile, strict: true, ignore: ['MISSING_PRIVATE_KEY'] })
26
+ const privateKey = dotenvx.get(privateKeyName, { path: '.env.keys', strict: true, ignore: ['MISSING_KEY'] })
27
+ const json = await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey).run()
28
+ removeEnvKey(privateKeyName)
29
+
30
+ return {
31
+ ...json,
32
+ changed: json.changed,
33
+ privateKeyName,
34
+ privateKeyValue: json.private_key
35
+ }
36
+ }
37
+ }
38
+
39
+ module.exports = ArmorUp