@dotenvx/dotenvx 1.58.0 → 1.59.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,14 @@
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/compare/v1.58.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.59.0...main)
6
+
7
+ ## [1.59.0](https://github.com/dotenvx/dotenvx/compare/v1.58.0...v1.59.0) (2026-03-28)
8
+
9
+ ### Changed
10
+
11
+ * `encrypt` and `set` now create a `.env` file if one does not exist ([#771](https://github.com/dotenvx/dotenvx/pull/771))
12
+ * pass `--no-create` to prevent file creation
6
13
 
7
14
  ## [1.58.0](https://github.com/dotenvx/dotenvx/compare/v1.57.5...v1.58.0) (2026-03-27)
8
15
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.58.0",
2
+ "version": "1.59.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -12,12 +12,13 @@ function encrypt () {
12
12
 
13
13
  const envs = this.envs
14
14
  const opsOn = options.opsOff !== true
15
+ const noCreate = options.create === false
15
16
 
16
17
  // stdout - should not have a try so that exit codes can surface to stdout
17
18
  if (options.stdout) {
18
19
  const {
19
20
  processedEnvs
20
- } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
21
+ } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn, noCreate).run()
21
22
 
22
23
  for (const processedEnv of processedEnvs) {
23
24
  console.log(processedEnv.envSrc)
@@ -29,7 +30,7 @@ function encrypt () {
29
30
  processedEnvs,
30
31
  changedFilepaths,
31
32
  unchangedFilepaths
32
- } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn).run()
33
+ } = new Encrypt(envs, options.key, options.excludeKey, options.envKeysFile, opsOn, noCreate).run()
33
34
 
34
35
  for (const processedEnv of processedEnvs) {
35
36
  logger.verbose(`encrypting ${processedEnv.envFilepath} (${processedEnv.filepath})`)
@@ -23,12 +23,13 @@ function set (key, value) {
23
23
  const envs = this.envs
24
24
  const envKeysFilepath = options.envKeysFile
25
25
  const opsOn = options.opsOff !== true
26
+ const noCreate = options.create === false
26
27
 
27
28
  const {
28
29
  processedEnvs,
29
30
  changedFilepaths,
30
31
  unchangedFilepaths
31
- } = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn).run()
32
+ } = new Sets(key, value, envs, encrypt, envKeysFilepath, opsOn, noCreate).run()
32
33
 
33
34
  let withEncryption = ''
34
35
 
@@ -116,6 +116,7 @@ program.command('set')
116
116
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
117
117
  .option('-c, --encrypt', 'encrypt value', true)
118
118
  .option('-p, --plain', 'store value as plain text', false)
119
+ .option('--no-create', 'do not create .env file(s) when missing')
119
120
  .option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
120
121
  .action(function (...args) {
121
122
  this.envs = envs
@@ -130,6 +131,7 @@ program.command('encrypt')
130
131
  .option('-fk, --env-keys-file <path>', 'path to your .env.keys file (default: same path as your env file)')
131
132
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
132
133
  .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
134
+ .option('--no-create', 'do not create .env file(s) when missing')
133
135
  .option('--ops-off', 'disable dotenvx-ops features', sesh.opsOff())
134
136
  .option('--stdout', 'send to stdout')
135
137
  .action(function (...args) {
@@ -0,0 +1,33 @@
1
+ const SAMPLE_ENV_KIT = `
2
+ # ── Database ─────────────────────────────────────
3
+ DATABASE_URL="postgresql://postgres:pass@db.ref.supabase.co:5432/postgres"
4
+
5
+ # ── Auth ─────────────────────────────────────────
6
+ AUTH0_CLIENT_ID="xxxx"
7
+ AUTH0_CLIENT_SECRET="xxxx"
8
+
9
+ # ── AI / LLM ────────────────────────────────────
10
+ OPENAI_API_KEY="sk-xxxx"
11
+ ANTHROPIC_API_KEY="sk-ant-xxxx"
12
+
13
+ # ── Email ────────────────────────────────────────
14
+ RESEND_API_KEY="re_xxxx"
15
+
16
+ # ── Cloud Storage ────────────────────────────────
17
+ AWS_ACCESS_KEY_ID="xxxx"
18
+ AWS_SECRET_ACCESS_KEY="xxxx"
19
+
20
+ # ── Analytics / Monitoring ───────────────────────
21
+ SENTRY_DSN="https://hex@o1234.ingest.us.sentry.io/1234567"
22
+
23
+ # ── Payments ─────────────────────────────────────
24
+ STRIPE_API_KEY="sk_test_xxxx"
25
+
26
+ # ── Feature Flags ────────────────────────────────
27
+ FLAGSMITH_ENV_ID="xxxx"
28
+
29
+ # ── CI/CD / Deployment ──────────────────────────
30
+ VERCEL_TOKEN="vcp_xxxx"
31
+ `.trimStart()
32
+
33
+ module.exports = SAMPLE_ENV_KIT
@@ -26,14 +26,16 @@ const {
26
26
  const replace = require('./../helpers/replace')
27
27
  const dotenvParse = require('./../helpers/dotenvParse')
28
28
  const detectEncoding = require('./../helpers/detectEncoding')
29
+ const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
29
30
 
30
31
  class Encrypt {
31
- constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
32
+ constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false, noCreate = false) {
32
33
  this.envs = determine(envs, process.env)
33
34
  this.key = key
34
35
  this.excludeKey = excludeKey
35
36
  this.envKeysFilepath = envKeysFilepath
36
37
  this.opsOn = opsOn
38
+ this.noCreate = noCreate
37
39
 
38
40
  this.processedEnvs = []
39
41
  this.changedFilepaths = new Set()
@@ -75,8 +77,17 @@ class Encrypt {
75
77
  row.envFilepath = envFilepath
76
78
 
77
79
  try {
80
+ // if noCreate is on then detectEncoding will throw and we'll halt the calls
81
+ // but if noCreate is false then create the file if it doesn't exist
82
+ if (!fsx.existsSync(filepath) && !this.noCreate) {
83
+ fsx.writeFileX(filepath, SAMPLE_ENV_KIT)
84
+ }
78
85
  const encoding = detectEncoding(filepath)
79
86
  let envSrc = fsx.readFileX(filepath, { encoding })
87
+ if (envSrc.trim().length === 0) {
88
+ envSrc = SAMPLE_ENV_KIT
89
+ row.kitCreated = 'sample'
90
+ }
80
91
  const envParsed = dotenvParse(envSrc)
81
92
 
82
93
  let publicKey
@@ -27,13 +27,14 @@ const dotenvParse = require('./../helpers/dotenvParse')
27
27
  const detectEncoding = require('./../helpers/detectEncoding')
28
28
 
29
29
  class Sets {
30
- constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false) {
30
+ constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false, noCreate = false) {
31
31
  this.envs = determine(envs, process.env)
32
32
  this.key = key
33
33
  this.value = value
34
34
  this.encrypt = encrypt
35
35
  this.envKeysFilepath = envKeysFilepath
36
36
  this.opsOn = opsOn
37
+ this.noCreate = noCreate
37
38
 
38
39
  this.processedEnvs = []
39
40
  this.changedFilepaths = new Set()
@@ -72,10 +73,30 @@ class Sets {
72
73
  row.changed = false
73
74
 
74
75
  try {
76
+ let seededWithInitialKey = false
77
+
78
+ if (!fsx.existsSync(filepath)) {
79
+ if (this.noCreate) {
80
+ detectEncoding(filepath) // throws ENOENT
81
+ } else {
82
+ fsx.writeFileX(filepath, '')
83
+ }
84
+ }
85
+
75
86
  const encoding = detectEncoding(filepath)
76
87
  let envSrc = fsx.readFileX(filepath, { encoding })
88
+
89
+ // blank files seeded by `set` should contain only the key being set
90
+ if (row.key && envSrc.trim().length === 0) {
91
+ envSrc = `${row.key}="${this.value}"\n`
92
+ seededWithInitialKey = true
93
+ }
94
+
77
95
  const envParsed = dotenvParse(envSrc)
78
96
  row.originalValue = envParsed[row.key] || null
97
+ if (seededWithInitialKey) {
98
+ row.originalValue = null
99
+ }
79
100
  const wasPlainText = !isEncrypted(row.originalValue)
80
101
  this.readableFilepaths.add(envFilepath)
81
102
 
@@ -119,7 +140,13 @@ class Sets {
119
140
 
120
141
  const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt
121
142
  const valueChanged = this.value !== row.originalValue
122
- if (goingFromPlainTextToEncrypted || valueChanged) {
143
+ const shouldPersistSeededPlainValue = seededWithInitialKey && !this.encrypt
144
+
145
+ if (shouldPersistSeededPlainValue) {
146
+ row.envSrc = envSrc
147
+ this.changedFilepaths.add(envFilepath)
148
+ row.changed = true
149
+ } else if (goingFromPlainTextToEncrypted || valueChanged) {
123
150
  row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value)
124
151
  this.changedFilepaths.add(envFilepath)
125
152
  row.changed = true