@dotenvx/dotenvx-ops 0.40.0 → 0.41.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,13 @@
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.41.0...main)
6
+
7
+ ## [0.41.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.40.0...v0.41.0) (2026-04-26)
8
+
9
+ ### Added
10
+
11
+ * Add `armor up` ([#58](https://github.com/dotenvx/dotenvx-ops/pull/58))
6
12
 
7
13
  ## [0.40.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.39.1...v0.40.0) (2026-04-24)
8
14
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.40.0",
2
+ "version": "0.41.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 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,7 +16,8 @@ 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
@@ -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 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