@dotenvx/dotenvx-ops 0.37.8 → 0.38.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/README.md +7 -3
- package/package.json +2 -1
- package/src/cli/actions/backup.js +2 -2
- package/src/cli/actions/keypair.js +9 -5
- package/src/cli/actions/login.js +12 -5
- package/src/cli/actions/open.js +2 -3
- package/src/cli/actions/rotate.js +9 -7
- package/src/cli/dotenvx-ops.js +3 -3
- package/src/db/session.js +43 -0
- package/src/lib/api/getVersion.js +24 -0
- package/src/lib/helpers/openUrl.js +7 -0
- package/src/lib/main.js +9 -9
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.
|
|
5
|
+
[Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.38.0...main)
|
|
6
|
+
|
|
7
|
+
## [0.38.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.37.9...v0.38.0) (2026-04-20)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add update notification `⛆ update available (patch)` when update is available - once every 24 hours ([#38](https://github.com/dotenvx/dotenvx-ops/pull/38))
|
|
12
|
+
|
|
13
|
+
## [0.37.9](https://github.com/dotenvx/dotenvx-ops/compare/v0.37.8...v0.37.9) (2026-04-17)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* Fix `open` when open command does not exist on user's machine ([#36](https://github.com/dotenvx/dotenvx-ops/pull/36))
|
|
6
18
|
|
|
7
19
|
## [0.37.8](https://github.com/dotenvx/dotenvx-ops/compare/v0.37.7...v0.37.8) (2026-04-08)
|
|
8
20
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
[](https://dotenvx.com/ops)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
```
|
|
4
|
+
⛨ ARMORED KEYS: Harden your private keys.
|
|
5
|
+
⮕ install [curl -sfS https://dotenvx.sh/ops | sh]
|
|
6
|
+
⮕ then run [dotenvx-ops login]
|
|
7
|
+
```
|
|
4
8
|
|
|
5
|
-
[Learn more](https://dotenvx.com/
|
|
9
|
+
[Learn more](https://dotenvx.com/ops)
|
|
6
10
|
|
|
7
11
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.38.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx-ops",
|
|
4
4
|
"description": "Secrets for agents–from the creator of `dotenv` and `dotenvx`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"express": "^5.2.1",
|
|
55
55
|
"open": "^8.4.2",
|
|
56
56
|
"playwright": "^1.57.0",
|
|
57
|
+
"semver": "^7.7.4",
|
|
57
58
|
"systeminformation": "^5.22.11",
|
|
58
59
|
"undici": "^7.11.0",
|
|
59
60
|
"yocto-spinner": "^1.1.0"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const open = require('open')
|
|
3
2
|
|
|
4
3
|
const { logger } = require('@dotenvx/dotenvx')
|
|
5
4
|
const Session = require('./../../db/session')
|
|
@@ -9,6 +8,7 @@ const clipboardy = require('./../../lib/helpers/clipboardy')
|
|
|
9
8
|
const confirm = require('./../../lib/helpers/confirm')
|
|
10
9
|
const formatCode = require('./../../lib/helpers/formatCode')
|
|
11
10
|
const truncate = require('./../../lib/helpers/truncate')
|
|
11
|
+
const openUrl = require('./../../lib/helpers/openUrl')
|
|
12
12
|
|
|
13
13
|
const LoggedIn = require('./../../lib/services/loggedIn')
|
|
14
14
|
const Login = require('./../../lib/services/login')
|
|
@@ -48,7 +48,7 @@ async function backup () {
|
|
|
48
48
|
|
|
49
49
|
// optionally allow user to open browser
|
|
50
50
|
confirm({ message: `press Enter to open [${verificationUri}] and enter code [${formatCode(userCode)}]...` })
|
|
51
|
-
.then(answer => answer &&
|
|
51
|
+
.then(answer => answer && openUrl(verificationUriComplete))
|
|
52
52
|
.catch(() => {}) // ignore
|
|
53
53
|
|
|
54
54
|
const data = await pollPromise
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { logger } = require('@dotenvx/dotenvx')
|
|
2
2
|
const Session = require('./../../db/session')
|
|
3
3
|
const createSpinner = require('../../lib/helpers/createSpinner2')
|
|
4
|
+
const PostKeypair = require('../../lib/api/postKeypair')
|
|
4
5
|
|
|
5
6
|
async function keypair (publicKey) {
|
|
6
7
|
// debug opts
|
|
@@ -13,14 +14,17 @@ async function keypair (publicKey) {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
const sesh = new Session()
|
|
17
|
+
await sesh.notifyUpdate()
|
|
16
18
|
const hostname = options.hostname || sesh.hostname()
|
|
17
|
-
const token = options.token
|
|
19
|
+
const token = options.token || sesh.token()
|
|
18
20
|
try {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const devicePublicKey = sesh.devicePublicKey()
|
|
22
|
+
|
|
23
|
+
const json = await new PostKeypair(hostname, token, devicePublicKey, publicKey).run()
|
|
24
|
+
|
|
21
25
|
const output = {
|
|
22
|
-
public_key:
|
|
23
|
-
private_key:
|
|
26
|
+
public_key: json.public_key,
|
|
27
|
+
private_key: json.private_key
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
let space = 0
|
package/src/cli/actions/login.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const open = require('open')
|
|
2
1
|
const { logger } = require('@dotenvx/dotenvx')
|
|
3
2
|
const Session = require('./../../db/session')
|
|
4
3
|
|
|
@@ -8,6 +7,7 @@ const LoginPoll = require('./../../lib/services/loginPoll')
|
|
|
8
7
|
const clipboardy = require('./../../lib/helpers/clipboardy')
|
|
9
8
|
const createSpinner2 = require('../../lib/helpers/createSpinner2')
|
|
10
9
|
const formatCode = require('./../../lib/helpers/formatCode')
|
|
10
|
+
const openUrl = require('./../../lib/helpers/openUrl')
|
|
11
11
|
|
|
12
12
|
const FRAMES = ['◐', '◓', '◑', '◒']
|
|
13
13
|
|
|
@@ -17,6 +17,7 @@ function listenForOpenKey (onOpen) {
|
|
|
17
17
|
|
|
18
18
|
const canSetRawMode = typeof stdin.setRawMode === 'function'
|
|
19
19
|
const wasRawMode = Boolean(stdin.isRaw)
|
|
20
|
+
let didHandleOpenChoice = false
|
|
20
21
|
|
|
21
22
|
const cleanup = () => {
|
|
22
23
|
stdin.off('data', onData)
|
|
@@ -35,12 +36,17 @@ function listenForOpenKey (onOpen) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
if (key === '\r' || key === '\n' || lower === 'y') {
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if (!didHandleOpenChoice) {
|
|
40
|
+
didHandleOpenChoice = true
|
|
41
|
+
Promise.resolve(onOpen()).catch(() => {})
|
|
42
|
+
}
|
|
40
43
|
return
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
if (lower === 'n')
|
|
46
|
+
if (lower === 'n') {
|
|
47
|
+
cleanup()
|
|
48
|
+
process.kill(process.pid, 'SIGINT')
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
if (canSetRawMode) stdin.setRawMode(true)
|
|
@@ -57,6 +63,7 @@ async function login () {
|
|
|
57
63
|
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
58
64
|
|
|
59
65
|
const sesh = new Session()
|
|
66
|
+
await sesh.notifyUpdate()
|
|
60
67
|
const hostname = options.hostname || sesh.hostname()
|
|
61
68
|
let cleanupOpenKeyListener = () => {}
|
|
62
69
|
|
|
@@ -78,7 +85,7 @@ async function login () {
|
|
|
78
85
|
|
|
79
86
|
// begin polling
|
|
80
87
|
const pollPromise = new LoginPoll(hostname, deviceCode, interval).run()
|
|
81
|
-
cleanupOpenKeyListener = listenForOpenKey(() =>
|
|
88
|
+
cleanupOpenKeyListener = listenForOpenKey(() => openUrl(verificationUriComplete))
|
|
82
89
|
const data = await pollPromise
|
|
83
90
|
|
|
84
91
|
cleanupOpenKeyListener()
|
package/src/cli/actions/open.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
const _open = require('open')
|
|
2
|
-
|
|
3
1
|
const { logger } = require('@dotenvx/dotenvx')
|
|
4
2
|
const Session = require('./../../db/session')
|
|
5
3
|
|
|
6
4
|
const dotenvxProjectId = require('./../../lib/helpers/dotenvxProjectId')
|
|
5
|
+
const openUrl = require('./../../lib/helpers/openUrl')
|
|
7
6
|
|
|
8
7
|
async function open () {
|
|
9
8
|
// debug opts
|
|
@@ -15,7 +14,7 @@ async function open () {
|
|
|
15
14
|
const uid = dotenvxProjectId(this.cwd)
|
|
16
15
|
const url = `${hostname}/go/${uid}`
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
await openUrl(url)
|
|
19
18
|
|
|
20
19
|
logger.success(`✔ opened [${url}]`)
|
|
21
20
|
} catch (error) {
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
const { logger } = require('@dotenvx/dotenvx')
|
|
2
2
|
const Session = require('./../../db/session')
|
|
3
3
|
const Rotate = require('./../../lib/services/rotate')
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
const spinner = createSpinner('rotating..')
|
|
4
|
+
const { createSpinner2 } = require('./../../lib/helpers/createSpinner2')
|
|
7
5
|
|
|
8
6
|
async function rotate (uri) {
|
|
9
7
|
const options = this.opts()
|
|
8
|
+
const spinner = await createSpinner2({ ...options, text: 'rotating' })
|
|
9
|
+
|
|
10
10
|
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
if (uri) {
|
|
12
|
+
logger.debug(`uri: ${uri}`)
|
|
13
|
+
}
|
|
11
14
|
|
|
12
15
|
try {
|
|
13
|
-
spinner.start()
|
|
14
|
-
|
|
15
16
|
const sesh = new Session()
|
|
17
|
+
await sesh.notifyUpdate()
|
|
16
18
|
const hostname = options.hostname || sesh.hostname()
|
|
17
19
|
const token = options.token || sesh.token()
|
|
18
20
|
|
|
19
21
|
const { url, rotUid } = await new Rotate(hostname, token, uri).run()
|
|
20
22
|
|
|
21
|
-
spinner.stop()
|
|
23
|
+
if (spinner) spinner.stop()
|
|
22
24
|
|
|
23
25
|
logger.success(`✔ rotated [${url}]`)
|
|
24
26
|
logger.help(`⮕ next run [dotenvx-ops get dotenvx://${rotUid}]`)
|
|
25
27
|
} catch (error) {
|
|
26
|
-
spinner.stop()
|
|
28
|
+
if (spinner) spinner.stop()
|
|
27
29
|
if (error.message) {
|
|
28
30
|
logger.error(error.message)
|
|
29
31
|
} else {
|
package/src/cli/dotenvx-ops.js
CHANGED
|
@@ -7,6 +7,8 @@ const program = new Command()
|
|
|
7
7
|
const { setLogLevel } = require('@dotenvx/dotenvx')
|
|
8
8
|
|
|
9
9
|
const packageJson = require('./../lib/helpers/packageJson')
|
|
10
|
+
const argv = process.argv.slice(2)
|
|
11
|
+
const firstArg = argv[0]
|
|
10
12
|
|
|
11
13
|
// global log levels
|
|
12
14
|
program
|
|
@@ -15,7 +17,7 @@ program
|
|
|
15
17
|
.option('-q, --quiet', 'sets log level to error')
|
|
16
18
|
.option('-v, --verbose', 'sets log level to verbose')
|
|
17
19
|
.option('-d, --debug', 'sets log level to debug')
|
|
18
|
-
.hook('preAction', (thisCommand, actionCommand) => {
|
|
20
|
+
.hook('preAction', async (thisCommand, actionCommand) => {
|
|
19
21
|
const options = thisCommand.opts()
|
|
20
22
|
|
|
21
23
|
setLogLevel(options)
|
|
@@ -90,8 +92,6 @@ program
|
|
|
90
92
|
.action(setAction)
|
|
91
93
|
|
|
92
94
|
// dotenvx-ops rotate
|
|
93
|
-
const argv = process.argv.slice(2)
|
|
94
|
-
const firstArg = argv[0]
|
|
95
95
|
const shouldLoadRotateCommand = firstArg === 'rotate' || firstArg === 'help' || firstArg === '--help' || firstArg === '-h'
|
|
96
96
|
if (shouldLoadRotateCommand) {
|
|
97
97
|
program.addCommand(require('./commands/rotate'))
|
package/src/db/session.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
const Conf = require('conf')
|
|
2
2
|
const dotenv = require('dotenv')
|
|
3
3
|
const si = require('systeminformation')
|
|
4
|
+
const semver = require('semver')
|
|
5
|
+
const { logger } = require('@dotenvx/dotenvx')
|
|
4
6
|
|
|
5
7
|
const Device = require('./device')
|
|
6
8
|
const jsonToEnv = require('./../lib/helpers/jsonToEnv')
|
|
9
|
+
const packageJson = require('./../lib/helpers/packageJson')
|
|
10
|
+
const GetVersion = require('./../lib/api/getVersion')
|
|
11
|
+
|
|
12
|
+
const HOURS_24 = 60 * 60 * 24 * 1000 // 24hrs in ms
|
|
7
13
|
|
|
8
14
|
class Session {
|
|
9
15
|
constructor () {
|
|
@@ -74,6 +80,41 @@ class Session {
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
//
|
|
84
|
+
// Notify Update
|
|
85
|
+
//
|
|
86
|
+
async notifyUpdate () {
|
|
87
|
+
try {
|
|
88
|
+
const lastCheck = Number(this.store.get('DOTENVX_OPS_VERSION_LAST_CHECK') || 0)
|
|
89
|
+
const now = Date.now()
|
|
90
|
+
|
|
91
|
+
// 24 hours have passed
|
|
92
|
+
if ((lastCheck + HOURS_24) < now) {
|
|
93
|
+
const local = packageJson.version
|
|
94
|
+
let remote = local // in case of http fetch error
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const VERSION = await new GetVersion().run()
|
|
98
|
+
remote = VERSION
|
|
99
|
+
this.store.set('DOTENVX_OPS_VERSION', VERSION) // remote version
|
|
100
|
+
} catch (err) {
|
|
101
|
+
// noop
|
|
102
|
+
logger.debug(err.message)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.store.set('DOTENVX_OPS_VERSION_LAST_CHECK', now) // record check
|
|
106
|
+
|
|
107
|
+
if (semver.gt(remote, local)) {
|
|
108
|
+
const diff = semver.diff(local, remote)
|
|
109
|
+
console.error(`⛆ update available (${diff})`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// noop
|
|
114
|
+
logger.debug(err.message)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
77
118
|
//
|
|
78
119
|
// Set/Delete
|
|
79
120
|
//
|
|
@@ -121,6 +162,8 @@ class Session {
|
|
|
121
162
|
this.store.delete('DOTENVX_OPS_TOKEN')
|
|
122
163
|
this.store.delete('DOTENVX_OPS_HOSTNAME')
|
|
123
164
|
this.store.delete('DOTENVX_OPS_ON')
|
|
165
|
+
this.store.delete('DOTENVX_OPS_VERSION')
|
|
166
|
+
this.store.delete('DOTENVX_OPS_VERSION_LAST_CHECK')
|
|
124
167
|
return true
|
|
125
168
|
}
|
|
126
169
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { http } = require('../../lib/helpers/http')
|
|
2
|
+
|
|
3
|
+
class GetVersion {
|
|
4
|
+
constructor () {
|
|
5
|
+
this.hostname = 'https://dotenvx.sh'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async run () {
|
|
9
|
+
const url = `${this.hostname}/ops/VERSION`
|
|
10
|
+
|
|
11
|
+
const resp = await http(url, { method: 'GET' })
|
|
12
|
+
const VERSION = await resp.body.text()
|
|
13
|
+
|
|
14
|
+
if (resp.statusCode >= 400) {
|
|
15
|
+
const error = new Error('[VERSION_FETCH_FAILED] version fetch failed')
|
|
16
|
+
error.code = 'VERSION_FETCH_FAILED'
|
|
17
|
+
throw error
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return VERSION
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = GetVersion
|
package/src/lib/main.js
CHANGED
|
@@ -14,18 +14,18 @@ const dotenvxProjectId = require('./helpers/dotenvxProjectId')
|
|
|
14
14
|
const observe = async function (encoded, options = {}) {
|
|
15
15
|
const sesh = new Session() // TODO: handle scenario where constructor fails
|
|
16
16
|
|
|
17
|
-
let hostname =
|
|
17
|
+
let hostname = options.hostname
|
|
18
18
|
if (!hostname) {
|
|
19
19
|
hostname = sesh.hostname()
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
let token =
|
|
22
|
+
let token = options.token
|
|
23
23
|
if (!token) {
|
|
24
24
|
token = sesh.token()
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const _pwd = process.cwd()
|
|
28
|
-
const _dotenvxProjectId =
|
|
28
|
+
const _dotenvxProjectId = options.dotenvxProjectId || dotenvxProjectId(_pwd, false)
|
|
29
29
|
|
|
30
30
|
const _gitUrl = gitUrl()
|
|
31
31
|
const _gitBranch = gitBranch()
|
|
@@ -45,12 +45,12 @@ const observe = async function (encoded, options = {}) {
|
|
|
45
45
|
const get = async function (uri, options = {}) {
|
|
46
46
|
const sesh = new Session() // TODO: handle scenario where constructor fails
|
|
47
47
|
|
|
48
|
-
let hostname =
|
|
48
|
+
let hostname = options.hostname
|
|
49
49
|
if (!hostname) {
|
|
50
50
|
hostname = sesh.hostname()
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
let token =
|
|
53
|
+
let token = options.token
|
|
54
54
|
if (!token) {
|
|
55
55
|
token = sesh.token()
|
|
56
56
|
}
|
|
@@ -62,12 +62,12 @@ const get = async function (uri, options = {}) {
|
|
|
62
62
|
const set = async function (uri, value, options = {}) {
|
|
63
63
|
const sesh = new Session() // TODO: handle scenario where constructor fails
|
|
64
64
|
|
|
65
|
-
let hostname =
|
|
65
|
+
let hostname = options.hostname
|
|
66
66
|
if (!hostname) {
|
|
67
67
|
hostname = sesh.hostname()
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
let token =
|
|
70
|
+
let token = options.token
|
|
71
71
|
if (!token) {
|
|
72
72
|
token = sesh.token()
|
|
73
73
|
}
|
|
@@ -80,12 +80,12 @@ const set = async function (uri, value, options = {}) {
|
|
|
80
80
|
const keypair = async function (publicKey, options = {}) {
|
|
81
81
|
const sesh = new Session() // TODO: handle scenario where constructor fails
|
|
82
82
|
|
|
83
|
-
let hostname =
|
|
83
|
+
let hostname = options.hostname
|
|
84
84
|
if (!hostname) {
|
|
85
85
|
hostname = sesh.hostname()
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
let token =
|
|
88
|
+
let token = options.token
|
|
89
89
|
if (!token) {
|
|
90
90
|
token = sesh.token()
|
|
91
91
|
}
|