@dotenvx/dotenvx-ops 0.18.0 → 0.20.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.18.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx-ops/compare/v0.20.0...main)
6
+
7
+ ## [0.20.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.19.0...v0.20.0) (2025-10-07)
8
+
9
+ ### Added
10
+
11
+ * Add `--force` flag to `sync` ([#4](https://github.com/dotenvx/dotenvx-ops/pull/4))
12
+
13
+ ## [0.19.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.18.0...v0.19.0) (2025-10-07)
14
+
15
+ ### Added
16
+
17
+ * Surface any conflicts during `sync` ([#3](https://github.com/dotenvx/dotenvx-ops/pull/3))
6
18
 
7
19
  ## [0.18.0](https://github.com/dotenvx/dotenvx-ops/compare/v0.17.1...v0.18.0) (2025-10-06)
8
20
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.18.0",
2
+ "version": "0.20.0",
3
3
  "name": "@dotenvx/dotenvx-ops",
4
4
  "description": "Dotenvx Ops",
5
5
  "author": "@motdotla",
@@ -5,18 +5,13 @@ const { logger } = require('@dotenvx/dotenvx')
5
5
 
6
6
  const { createSpinner } = require('./../../lib/helpers/createSpinner')
7
7
  const smartMask = require('./../../lib/helpers/smartMask')
8
+ const pluralize = require('./../../lib/helpers/pluralize')
8
9
 
9
10
  const Sync = require('./../../lib/services/sync')
10
-
11
- const GetSynchronization = require('./../../lib/api/getSynchronization')
11
+ const SyncConflict = require('./../../lib/services/syncConflict')
12
12
 
13
13
  const spinner = createSpinner('syncing')
14
14
 
15
- // move soon
16
- const Session = require('./../../db/session')
17
- const sesh = new Session()
18
- const token = sesh.token()
19
-
20
15
  function sha256File (filepath) {
21
16
  const buf = fs.readFileSync(filepath)
22
17
  return crypto.createHash('sha256').update(buf).digest('hex')
@@ -35,7 +30,7 @@ async function sync () {
35
30
  dotenvxProjectId,
36
31
  projectUsernameName,
37
32
  files
38
- } = await new Sync(options.hostname).run()
33
+ } = await new Sync(options.hostname, options.force).run()
39
34
  logger.debug(`files: ${JSON.stringify(files)}`)
40
35
 
41
36
  // write output files
@@ -73,16 +68,36 @@ async function sync () {
73
68
  logger.debug(error.stack)
74
69
  }
75
70
 
76
- // display conflict diff
77
- if (error.code === 'DOTENVX_SYNC_CONFLICT') {
78
- const synchronizationId = error.meta.synchronization_id
79
- const data = await new GetSynchronization(options.hostname, token, synchronizationId).run()
80
- const conflictedFile = data.files.find(f => f.status === 'conflicted')
81
- if (conflictedFile) {
82
- console.log(conflictedFile.filepath)
83
- console.log(conflictedFile.diff_ansi)
71
+ try {
72
+ if (error.code === 'DOTENVX_SYNC_CONFLICT') {
73
+ spinner.start()
74
+
75
+ const synchronizationId = error.meta.synchronization_id
76
+ const { conflictedFiles } = await new SyncConflict(options.hostname, synchronizationId).run()
77
+
78
+ spinner.stop()
79
+
80
+ // loop through any conflicted files
81
+ for (const conflictedFile of conflictedFiles) {
82
+ logger.info('')
83
+ logger.info(`✖ conflict [${conflictedFile.filepath}]`)
84
+ console.log(conflictedFile.diffAnsi)
85
+ }
86
+
87
+ logger.info(`Review and edit the ${pluralize('file', conflictedFiles.length)} as needed, then confirm your version with [dotenvx-ops sync --force]`)
88
+ }
89
+ } catch (error) {
90
+ spinner.stop()
91
+ if (error.message) {
92
+ logger.error(error.message)
84
93
  } else {
85
- console.log('No conflicted files found.')
94
+ logger.error(error)
95
+ }
96
+ if (error.help) {
97
+ logger.help(error.help)
98
+ }
99
+ if (error.stack) {
100
+ logger.debug(error.stack)
86
101
  }
87
102
  }
88
103
 
@@ -50,6 +50,7 @@ program
50
50
  .description('sync .env file(s)')
51
51
  .option('-h, --hostname <url>', 'set hostname', sesh.hostname())
52
52
  .option('--unmask', 'unmask DOTENVX_PROJECT_ID')
53
+ .option('--force', 'force changes')
53
54
  .action(syncAction)
54
55
 
55
56
  // dotenvx-ops login
@@ -3,7 +3,7 @@ const buildApiError = require('../../lib/helpers/buildApiError')
3
3
  const packageJson = require('../../lib/helpers/packageJson')
4
4
 
5
5
  class PostSync {
6
- constructor (hostname, token, devicePublicKey, encoded, dotenvxProjectId, pwd = null, gitUrl = null, gitBranch = null, systemUuid = null, osPlatform = null, osArch = null) {
6
+ constructor (hostname, token, devicePublicKey, encoded, dotenvxProjectId, pwd = null, gitUrl = null, gitBranch = null, systemUuid = null, osPlatform = null, osArch = null, force = false) {
7
7
  this.hostname = hostname || 'https://ops.dotenvx.com'
8
8
  this.token = token
9
9
  this.devicePublicKey = devicePublicKey
@@ -15,6 +15,7 @@ class PostSync {
15
15
  this.systemUuid = systemUuid
16
16
  this.osPlatform = osPlatform
17
17
  this.osArch = osArch
18
+ this.force = force
18
19
  }
19
20
 
20
21
  async run () {
@@ -30,6 +31,7 @@ class PostSync {
30
31
  const systemUuid = this.systemUuid
31
32
  const osPlatform = this.osPlatform
32
33
  const osArch = this.osArch
34
+ const force = this.force
33
35
 
34
36
  const resp = await http(url, {
35
37
  method: 'POST',
@@ -48,7 +50,8 @@ class PostSync {
48
50
  system_uuid: systemUuid,
49
51
  os_platform: osPlatform,
50
52
  os_arch: osArch,
51
- cli_version: packageJson.version
53
+ cli_version: packageJson.version,
54
+ force
52
55
  })
53
56
  })
54
57
 
@@ -0,0 +1,10 @@
1
+ function pluralize (word, count) {
2
+ // simple pluralization: add 's' at the end
3
+ if (count === 0 || count > 1) {
4
+ return word + 's'
5
+ } else {
6
+ return word
7
+ }
8
+ }
9
+
10
+ module.exports = pluralize
@@ -13,8 +13,9 @@ const Errors = require('./../helpers/errors')
13
13
  const PostSync = require('./../api/postSync')
14
14
 
15
15
  class Sync {
16
- constructor (hostname) {
16
+ constructor (hostname, force = false) {
17
17
  this.hostname = hostname
18
+ this.force = force
18
19
  this.cwd = process.cwd()
19
20
  }
20
21
 
@@ -42,7 +43,7 @@ class Sync {
42
43
  const _osPlatform = osInfo.platform
43
44
  const _osArch = osInfo.arch
44
45
 
45
- const data = await new PostSync(this.hostname, token, devicePublicKey, encoded, dotenvxProjectId, _pwd, _gitUrl, _gitBranch, _systemUuid, _osPlatform, _osArch).run()
46
+ const data = await new PostSync(this.hostname, token, devicePublicKey, encoded, dotenvxProjectId, _pwd, _gitUrl, _gitBranch, _systemUuid, _osPlatform, _osArch, this.force).run()
46
47
 
47
48
  return {
48
49
  id: data.id,
@@ -0,0 +1,36 @@
1
+ const Session = require('./../../db/session')
2
+
3
+ // api calls
4
+ const GetSynchronization = require('./../api/getSynchronization')
5
+
6
+ class SyncConflict {
7
+ constructor (hostname, synchronizationId) {
8
+ this.hostname = hostname
9
+ this.synchronizationId = synchronizationId
10
+ }
11
+
12
+ async run () {
13
+ const sesh = new Session()
14
+ const token = sesh.token()
15
+ const synchronizationId = this.synchronizationId
16
+
17
+ const data = await new GetSynchronization(this.hostname, token, synchronizationId).run()
18
+
19
+ const conflictedFiles = []
20
+ for (const file of data.files) {
21
+ if (file.status === 'conflicted') {
22
+ conflictedFiles.push({
23
+ filepath: file.filepath,
24
+ diffAnsi: file.diff_ansi
25
+ })
26
+ }
27
+ }
28
+
29
+ return {
30
+ id: data.id,
31
+ conflictedFiles
32
+ }
33
+ }
34
+ }
35
+
36
+ module.exports = SyncConflict