@dotenvx/dotenvx-ops 0.23.2 → 0.23.4

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.23.2...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.23.4...main)
6
+
7
+ ## [0.23.4](https://github.com/dotenvx/dotenvx-ops/compare/v0.23.3...v0.23.4) (2025-12-10)
8
+
9
+ ### Added
10
+
11
+ * Add working `rotate URI` command ([#13](https://github.com/dotenvx/dotenvx-ops/pull/13))
12
+
13
+ ## [0.23.3](https://github.com/dotenvx/dotenvx-ops/compare/v0.23.2...v0.23.3) (2025-12-09)
14
+
15
+ ### Changed
16
+
17
+ * Rename `rotate npm login` to `rotate npm connect` ([#12](https://github.com/dotenvx/dotenvx-ops/pull/12))
6
18
 
7
19
  ## [0.23.2](https://github.com/dotenvx/dotenvx-ops/compare/v0.23.1...v0.23.2) (2025-12-04)
8
20
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.23.2",
2
+ "version": "0.23.4",
3
3
  "name": "@dotenvx/dotenvx-ops",
4
4
  "description": "Dotenvx Ops – commercial tooling for .env files",
5
5
  "author": "@motdotla",
@@ -0,0 +1,46 @@
1
+ const { logger } = require('@dotenvx/dotenvx')
2
+ const Session = require('./../../../../db/session')
3
+ const RotateNpmConnect = require('./../../../../lib/services/rotateNpmConnect')
4
+ const { createSpinner } = require('./../../../../lib/helpers/createSpinner')
5
+
6
+ const spinner = createSpinner('waiting on browser completion')
7
+
8
+ async function connect () {
9
+ const options = this.opts()
10
+ logger.debug(`options: ${JSON.stringify(options)}`)
11
+
12
+ try {
13
+ spinner.start()
14
+
15
+ const sesh = new Session()
16
+ const hostname = options.hostname
17
+ const token = options.token || sesh.token()
18
+ const org = options.org
19
+ const username = options.username
20
+ const password = options.password
21
+ const email = options.email
22
+
23
+ const { uid, url } = await new RotateNpmConnect(hostname, token, org, username, password, email).run()
24
+
25
+ spinner.stop()
26
+
27
+ logger.success(`✔ connected [${url}]`)
28
+ logger.help(`⮕ next run [dotenvx-ops rotate dotenvx://${uid}]`)
29
+ } catch (error) {
30
+ spinner.stop()
31
+ if (error.message) {
32
+ logger.error(error.message)
33
+ } else {
34
+ logger.error(error)
35
+ }
36
+ if (error.help) {
37
+ logger.help(error.help)
38
+ }
39
+ if (error.stack) {
40
+ logger.debug(error.stack)
41
+ }
42
+ process.exit(1)
43
+ }
44
+ }
45
+
46
+ module.exports = connect
@@ -0,0 +1,42 @@
1
+ const { logger } = require('@dotenvx/dotenvx')
2
+ const Session = require('./../../db/session')
3
+ const Rotate = require('./../../lib/services/rotate')
4
+ const { createSpinner } = require('./../../lib/helpers/createSpinner')
5
+
6
+ const spinner = createSpinner('rotating..')
7
+
8
+ async function rotate (uri) {
9
+ const options = this.opts()
10
+ logger.debug(`options: ${JSON.stringify(options)}`)
11
+
12
+ try {
13
+ spinner.start()
14
+
15
+ const sesh = new Session()
16
+ const hostname = options.hostname
17
+ const token = options.token || sesh.token()
18
+
19
+ const { url, rotUid } = await new Rotate(hostname, token, uri).run()
20
+
21
+ spinner.stop()
22
+
23
+ logger.success(`✔ rotated [${url}]`)
24
+ logger.help(`⮕ next run [dotenvx-ops get dotenvx://${rotUid}]`)
25
+ } catch (error) {
26
+ spinner.stop()
27
+ if (error.message) {
28
+ logger.error(error.message)
29
+ } else {
30
+ logger.error(error)
31
+ }
32
+ if (error.help) {
33
+ logger.help(error.help)
34
+ }
35
+ if (error.stack) {
36
+ logger.debug(error.stack)
37
+ }
38
+ process.exit(1)
39
+ }
40
+ }
41
+
42
+ module.exports = rotate
@@ -2,26 +2,31 @@ const { Command } = require('commander')
2
2
 
3
3
  const npm = new Command('npm')
4
4
 
5
+ const Session = require('./../../../db/session')
6
+ const sesh = new Session()
7
+
5
8
  npm
6
9
  .description('npm')
7
10
  .allowUnknownOption()
8
11
 
9
- // dotenvx-ops rotate npm login
10
- const loginAction = require('./../../actions/rotate/npm/login')
12
+ // dotenvx-ops rotate npm connect
13
+ const connectAction = require('./../../actions/rotate/npm/connect')
11
14
  npm
12
- .command('login')
15
+ .command('connect')
13
16
  .description('connect passcard')
14
17
  .requiredOption('--org <organizationSlug>')
15
18
  .requiredOption('--username <username>')
16
19
  .requiredOption('--password <password>')
17
20
  .option('--email <email>')
18
- .action(loginAction)
21
+ .option('--hostname <url>', 'set hostname', sesh.hostname())
22
+ .option('--token <token>', 'set token')
23
+ .action(connectAction)
19
24
 
20
- // dotenvx-ops rotate npm rotate
21
- const rotateAction = require('./../../actions/rotate/npm/rotate')
22
- npm
23
- .command('rotate')
24
- .description('rotate api key')
25
- .action(rotateAction)
25
+ // // dotenvx-ops rotate npm rotate
26
+ // const rotateAction = require('./../../actions/rotate/npm/rotate')
27
+ // npm
28
+ // .command('rotate')
29
+ // .description('rotate api key')
30
+ // .action(rotateAction)
26
31
 
27
32
  module.exports = npm
@@ -2,10 +2,29 @@ const { Command } = require('commander')
2
2
 
3
3
  const rotate = new Command('rotate')
4
4
 
5
+ const Session = require('./../../db/session')
6
+ const sesh = new Session()
7
+
5
8
  rotate
6
- .description('rotate api keys')
9
+ .description('rotate secret')
7
10
  .allowUnknownOption()
8
11
 
9
12
  rotate.addCommand(require('./rotate/npm'))
10
13
 
14
+ // dotenvx-ops rotate (fallback positional argument handler)
15
+ const rotateAction = require('../actions/rotate')
16
+ rotate
17
+ .argument('[URI]', 'URI') // brackets = optional
18
+ .option('--hostname <url>', 'set hostname', sesh.hostname())
19
+ .option('--token <token>', 'set token')
20
+ .action(async function (uri, options, command) {
21
+ if (!uri) {
22
+ command.help()
23
+ return
24
+ }
25
+
26
+ // Call external action with correct "this" binding
27
+ await rotateAction.call(this, uri)
28
+ })
29
+
11
30
  module.exports = rotate
@@ -53,9 +53,6 @@ program
53
53
  .option('--force', 'force changes')
54
54
  .action(syncAction)
55
55
 
56
- // dotenvx-ops rotate
57
- program.addCommand(require('./commands/rotate'))
58
-
59
56
  // dotenvx-ops get
60
57
  const getAction = require('./actions/get')
61
58
  program
@@ -66,6 +63,9 @@ program
66
63
  .option('--token <token>', 'set token')
67
64
  .action(getAction)
68
65
 
66
+ // dotenvx-ops rotate
67
+ program.addCommand(require('./commands/rotate'))
68
+
69
69
  // dotenvx-ops login
70
70
  const loginAction = require('./actions/login')
71
71
  program
@@ -0,0 +1,39 @@
1
+ const { http } = require('../../lib/helpers/http')
2
+ const buildApiError = require('../../lib/helpers/buildApiError')
3
+
4
+ class PostRotate {
5
+ constructor (hostname, token, uri) {
6
+ this.hostname = hostname || 'https://ops.dotenvx.com'
7
+ this.token = token
8
+
9
+ this.uri = uri
10
+ }
11
+
12
+ async run () {
13
+ const token = this.token
14
+ const url = `${this.hostname}/api/rotate`
15
+
16
+ const uri = this.uri
17
+
18
+ const resp = await http(url, {
19
+ method: 'POST',
20
+ headers: {
21
+ Authorization: `Bearer ${token}`,
22
+ 'Content-Type': 'application/json'
23
+ },
24
+ body: JSON.stringify({
25
+ uri
26
+ })
27
+ })
28
+
29
+ const json = await resp.body.json()
30
+
31
+ if (resp.statusCode >= 400) {
32
+ throw buildApiError(resp.statusCode, json)
33
+ }
34
+
35
+ return json
36
+ }
37
+ }
38
+
39
+ module.exports = PostRotate
@@ -1,7 +1,7 @@
1
1
  const { http } = require('../../lib/helpers/http')
2
2
  const buildApiError = require('../../lib/helpers/buildApiError')
3
3
 
4
- class PostRotateLogin {
4
+ class PostRotateConnect {
5
5
  constructor (hostname, token, org, slug, username, password, email = null, playwrightStorageStateStringified) {
6
6
  this.hostname = hostname || 'https://ops.dotenvx.com'
7
7
  this.token = token
@@ -16,7 +16,7 @@ class PostRotateLogin {
16
16
 
17
17
  async run () {
18
18
  const token = this.token
19
- const url = `${this.hostname}/api/rotate/login`
19
+ const url = `${this.hostname}/api/rotate/connect`
20
20
 
21
21
  const org = this.org
22
22
  const slug = this.slug
@@ -51,4 +51,4 @@ class PostRotateLogin {
51
51
  }
52
52
  }
53
53
 
54
- module.exports = PostRotateLogin
54
+ module.exports = PostRotateConnect
@@ -1,9 +1,19 @@
1
1
  const { logger } = require('@dotenvx/dotenvx')
2
2
  const { chromium } = require('playwright')
3
3
 
4
+ const VIEWPORT_WIDTH = 1200
5
+ const VIEWPORT_HEIGHT = 742
6
+ const VIEWPORT = { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
7
+ const CHROME_ARGS = [
8
+ '--app=data:,', // starts a blank popup
9
+ '--disable-infobars',
10
+ '--disable-session-crashed-bubble',
11
+ '--noerrdialogs'
12
+ ]
13
+
4
14
  async function playwrightConnect (channel = 'chrome') {
5
15
  const browser = await ensurePlaywrightBrowser(channel)
6
- const context = await browser.newContext()
16
+ const context = await browser.newContext({ viewport: VIEWPORT })
7
17
  const page = await context.newPage()
8
18
 
9
19
  return { browser, context, page }
@@ -11,13 +21,13 @@ async function playwrightConnect (channel = 'chrome') {
11
21
 
12
22
  async function ensurePlaywrightBrowser (channel = 'chrome') {
13
23
  try {
14
- return await chromium.launch({ headless: false, channel })
24
+ return await chromium.launch({ headless: false, channel, args: CHROME_ARGS })
15
25
  } catch (err) {
16
26
  if (String(err).includes('Executable doesn\'t exist')) {
17
27
  logger.info('Installing chromium...')
18
28
  const cp = require('child_process')
19
29
  cp.execSync('npx playwright install chromium', { stdio: 'inherit' })
20
- return await chromium.launch({ headless: false })
30
+ return await chromium.launch({ headless: false, args: CHROME_ARGS })
21
31
  }
22
32
  }
23
33
  }
@@ -0,0 +1,28 @@
1
+ const PostRotate = require('./../api/postRotate')
2
+
3
+ class Rotate {
4
+ constructor (hostname, token, inputUri) {
5
+ this.hostname = hostname
6
+ this.token = token
7
+ this.inputUri = inputUri
8
+ }
9
+
10
+ async run () {
11
+ const hostname = this.hostname
12
+ const token = this.token
13
+ const inputUri = this.inputUri
14
+
15
+ const json = await new PostRotate(hostname, token, inputUri).run()
16
+ const uri = json.uri
17
+ const url = json.url
18
+ const rotUid = json.rot_uid
19
+
20
+ return {
21
+ uri,
22
+ url,
23
+ rotUid
24
+ }
25
+ }
26
+ }
27
+
28
+ module.exports = Rotate
@@ -0,0 +1,56 @@
1
+ const PostRotateConnect = require('./../api/postRotateConnect')
2
+
3
+ const playwrightConnect = require('./../helpers/playwrightConnect')
4
+
5
+ const TIMEOUT = 500 // visibility timeout
6
+
7
+ class RotateNpmConnect {
8
+ constructor (hostname, token, org, username, password, email = null) {
9
+ this.hostname = hostname
10
+ this.org = org
11
+ this.token = token
12
+ this.username = username
13
+ this.password = password
14
+
15
+ // optional
16
+ this.email = email
17
+ }
18
+
19
+ async run () {
20
+ const hostname = this.hostname
21
+ const token = this.token
22
+ const org = this.org
23
+ const slug = 'npm' // hardcoded
24
+ const username = this.username
25
+ const password = this.password
26
+ const email = this.email
27
+
28
+ const { browser, context, page } = await playwrightConnect()
29
+
30
+ const usernameSelector = 'input[type="text"]'
31
+ const passwordSelector = 'input[type="password"]'
32
+
33
+ await page.goto('https://npmjs.com/login', { waitUntil: 'domcontentloaded' })
34
+ await page.waitForTimeout(TIMEOUT)
35
+ await page.fill(usernameSelector, username)
36
+ await page.waitForTimeout(TIMEOUT)
37
+ await page.fill(passwordSelector, password)
38
+ await page.waitForTimeout(TIMEOUT)
39
+ await page.press('input[type="password"]', 'Enter')
40
+ await page.waitForNavigation({ timeout: 0 })
41
+ await page.waitForSelector('img[alt="avatar"]', { state: 'visible', timeout: 0 })
42
+
43
+ const playwrightStorageStateStringified = JSON.stringify(await context.storageState())
44
+
45
+ const { uid, url } = await new PostRotateConnect(hostname, token, org, slug, username, password, email, playwrightStorageStateStringified).run()
46
+
47
+ await browser.close()
48
+
49
+ return {
50
+ uid,
51
+ url
52
+ }
53
+ }
54
+ }
55
+
56
+ module.exports = RotateNpmConnect
@@ -1,47 +0,0 @@
1
- const { logger } = require('@dotenvx/dotenvx')
2
- const PostRotateLogin = require('./../../../../lib/api/postRotateLogin')
3
- const Session = require('./../../../../db/session')
4
- const playwrightConnect = require('./../../../../lib/helpers/playwrightConnect')
5
-
6
- const TIMEOUT = 500 // visibility timeout
7
-
8
- async function login () {
9
- const options = this.opts()
10
- logger.debug(`options: ${JSON.stringify(options)}`)
11
- const slug = 'npm'
12
- const { org, username, password, email } = options
13
- const { browser, context, page } = await playwrightConnect()
14
-
15
- const usernameSelector = 'input[type="text"]'
16
- const passwordSelector = 'input[type="password"]'
17
-
18
- await page.goto('https://npmjs.com/login', { waitUntil: 'domcontentloaded' })
19
- await page.waitForTimeout(TIMEOUT)
20
- await page.fill(usernameSelector, username)
21
- await page.waitForTimeout(TIMEOUT)
22
- await page.fill(passwordSelector, password)
23
- await page.waitForTimeout(TIMEOUT)
24
- await page.press('input[type="password"]', 'Enter')
25
- await page.waitForNavigation({ timeout: 0 })
26
- await page.waitForSelector('img[alt="avatar"]', { state: 'visible', timeout: 0 })
27
-
28
- const sesh = new Session() // TODO: handle scenario where constructor fails
29
-
30
- let hostname = process.env.DOTENVX_OPS_HOSTNAME || process.env.DOTENVX_RADAR_HOSTNAME || options.hostname
31
- if (!hostname) {
32
- hostname = sesh.hostname()
33
- }
34
-
35
- let token = process.env.DOTENVX_OPS_TOKEN || process.env.DOTENVX_RADAR_TOKEN || options.token
36
- if (!token) {
37
- token = sesh.token()
38
- }
39
-
40
- const playwrightStorageStateStringified = JSON.stringify(await context.storageState())
41
- const { uid } = await new PostRotateLogin(hostname, token, org, slug, username, password, email, playwrightStorageStateStringified).run()
42
-
43
- await browser.close()
44
- process.stdout.write(uid)
45
- }
46
-
47
- module.exports = login
@@ -1,9 +0,0 @@
1
- const { logger } = require('@dotenvx/dotenvx')
2
-
3
- async function rotate () {
4
- const options = this.opts()
5
- logger.debug(`options: ${JSON.stringify(options)}`)
6
- process.stdout.write('coming soon')
7
- }
8
-
9
- module.exports = rotate