lazy-vault 1.0.0 → 2.0.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/README.md +143 -79
- package/dist/cli/bin.js +214 -119
- package/dist/cli/bin.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,47 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# lazy-vault
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Security for the lazy developer.**
|
|
8
|
+
Stop worrying about sharing `.env` files. `lazy-vault` encrypts your secrets so you can safely commit them to Git.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
No cloud. No accounts. No lock-in.
|
|
10
|
+
Now with **Smart Profiles** and **Project Configuration**.
|
|
12
11
|
|
|
13
12
|
---
|
|
14
13
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
Environment variables are:
|
|
14
|
+
## What is lazy-vault?
|
|
18
15
|
|
|
19
|
-
-
|
|
20
|
-
- Sensitive
|
|
21
|
-
- Painful to share across machines and teams
|
|
16
|
+
`lazy-vault` is a CLI tool for **secure environment variable management**:
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
- Encrypt `.env` files
|
|
19
|
+
- Commit encrypted secrets to Git
|
|
20
|
+
- Sync secrets across machines safely
|
|
21
|
+
- Manage multiple environments (dev, prod, staging)
|
|
22
|
+
- Use strong cryptography without complexity
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
No cloud.
|
|
25
|
+
No accounts.
|
|
26
|
+
No vendor lock-in.
|
|
27
|
+
Your password never leaves your machine.
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
|
29
31
|
## Core Features
|
|
30
32
|
|
|
31
33
|
- **Strong Encryption**
|
|
32
|
-
|
|
34
|
+
AES-256-GCM + Argon2id (memory-hard key derivation)
|
|
35
|
+
|
|
36
|
+
- **Git-Safe Workflow**
|
|
37
|
+
Commit `.env.enc`, never `.env`
|
|
33
38
|
|
|
34
|
-
- **
|
|
35
|
-
|
|
39
|
+
- **Smart Profiles (v2)**
|
|
40
|
+
Security modes for speed vs paranoia
|
|
36
41
|
|
|
37
|
-
- **
|
|
38
|
-
|
|
42
|
+
- **Project Configuration (v2)**
|
|
43
|
+
Multi-environment support via config file
|
|
39
44
|
|
|
40
|
-
- **Merge-
|
|
41
|
-
Remote secrets override conflicts, local-only keys are preserved
|
|
45
|
+
- **Merge-Safe Syncing**
|
|
46
|
+
Remote secrets override conflicts, local-only keys are preserved
|
|
42
47
|
|
|
43
|
-
- **
|
|
44
|
-
|
|
48
|
+
- **Automation Ready**
|
|
49
|
+
Headless mode for CI/CD and deployments
|
|
45
50
|
|
|
46
51
|
---
|
|
47
52
|
|
|
@@ -51,7 +56,7 @@ Environment variables are:
|
|
|
51
56
|
npm install -g lazy-vault
|
|
52
57
|
```
|
|
53
58
|
|
|
54
|
-
Or
|
|
59
|
+
Or without installing:
|
|
55
60
|
|
|
56
61
|
```bash
|
|
57
62
|
npx lazy-vault
|
|
@@ -59,121 +64,180 @@ npx lazy-vault
|
|
|
59
64
|
|
|
60
65
|
---
|
|
61
66
|
|
|
62
|
-
|
|
67
|
+
# Quick Start
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
## Initialize (Optional)
|
|
70
|
+
|
|
71
|
+
Create a project config for multi-environment setups:
|
|
65
72
|
|
|
66
73
|
```bash
|
|
67
|
-
lazy-vault
|
|
74
|
+
lazy-vault init
|
|
68
75
|
```
|
|
69
76
|
|
|
70
|
-
|
|
77
|
+
Creates:
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
```json
|
|
80
|
+
lazy.config.json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
## Lock (Encrypt)
|
|
86
|
+
|
|
87
|
+
When you add new secrets:
|
|
77
88
|
|
|
78
89
|
```bash
|
|
79
|
-
|
|
80
|
-
git commit -m "Add encrypted env"
|
|
90
|
+
lazy-vault lock
|
|
81
91
|
```
|
|
82
92
|
|
|
93
|
+
### What it does:
|
|
94
|
+
|
|
95
|
+
- Encrypts `.env` → `.env.enc`
|
|
96
|
+
- Uses **AES-256-GCM + Argon2id**
|
|
97
|
+
- Adds `.env` to `.gitignore`
|
|
98
|
+
- Safe to commit `.env.enc`
|
|
99
|
+
|
|
83
100
|
---
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
## Sync (Decrypt & Merge)
|
|
103
|
+
|
|
104
|
+
When pulling code or deploying:
|
|
86
105
|
|
|
87
106
|
```bash
|
|
88
107
|
lazy-vault sync
|
|
89
108
|
```
|
|
90
109
|
|
|
91
|
-
|
|
92
|
-
- `.env` will be created or merged automatically
|
|
110
|
+
### What it does:
|
|
93
111
|
|
|
94
|
-
|
|
112
|
+
- Decrypts `.env.enc`
|
|
113
|
+
- Merges into `.env`
|
|
114
|
+
|
|
115
|
+
**Smart Merge Logic:**
|
|
95
116
|
|
|
96
|
-
|
|
117
|
+
- Remote keys overwrite local conflicts
|
|
118
|
+
- Local-only keys are preserved
|
|
97
119
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
# Configuration & Profiles (v2)
|
|
123
|
+
|
|
124
|
+
## Project Configuration
|
|
125
|
+
|
|
126
|
+
`lazy.config.json`
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"default": {
|
|
131
|
+
"source": ".env",
|
|
132
|
+
"output": ".env.enc",
|
|
133
|
+
"security": "light"
|
|
134
|
+
},
|
|
135
|
+
"production": {
|
|
136
|
+
"source": ".env.prod",
|
|
137
|
+
"output": ".env.prod.enc",
|
|
138
|
+
"security": "heavy"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
105
142
|
|
|
106
|
-
|
|
143
|
+
Now you can run:
|
|
107
144
|
|
|
108
|
-
|
|
145
|
+
```bash
|
|
146
|
+
lazy-vault lock production
|
|
147
|
+
lazy-vault sync production
|
|
148
|
+
```
|
|
109
149
|
|
|
110
150
|
---
|
|
111
151
|
|
|
112
|
-
## Security
|
|
152
|
+
## Security Profiles
|
|
113
153
|
|
|
114
|
-
|
|
115
|
-
`lazy-vault` cannot recover your password.
|
|
154
|
+
Trade speed for paranoia.
|
|
116
155
|
|
|
117
|
-
|
|
118
|
-
Tampered or corrupted files will fail to decrypt.
|
|
156
|
+
### Light (default)
|
|
119
157
|
|
|
120
|
-
-
|
|
121
|
-
|
|
158
|
+
- Fast (~0.5s)
|
|
159
|
+
- Optimized for frequent dev usage
|
|
122
160
|
|
|
123
|
-
|
|
124
|
-
|
|
161
|
+
### Heavy
|
|
162
|
+
|
|
163
|
+
- Slow (~1s+)
|
|
164
|
+
- Uses ~256MB RAM
|
|
165
|
+
- GPU-resistant
|
|
166
|
+
- Designed for production secrets
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
lazy-vault lock --profile heavy
|
|
170
|
+
```
|
|
125
171
|
|
|
126
172
|
---
|
|
127
173
|
|
|
128
|
-
|
|
174
|
+
# Automation & CI (Headless Mode)
|
|
175
|
+
|
|
176
|
+
For scripts, pipelines, and deployments:
|
|
129
177
|
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
178
|
+
```bash
|
|
179
|
+
export LAZY_VAULT_PASSWORD="your-secure-password"
|
|
180
|
+
lazy-vault sync
|
|
181
|
+
```
|
|
133
182
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
183
|
+
PowerShell:
|
|
184
|
+
|
|
185
|
+
```powershell
|
|
186
|
+
$env:LAZY_VAULT_PASSWORD="your-secure-password"
|
|
187
|
+
lazy-vault sync
|
|
137
188
|
```
|
|
138
189
|
|
|
139
|
-
|
|
190
|
+
No interactive prompts.
|
|
191
|
+
Safe for CI/CD.
|
|
140
192
|
|
|
141
193
|
---
|
|
142
194
|
|
|
143
|
-
|
|
195
|
+
# 🛠 CLI Reference
|
|
144
196
|
|
|
145
|
-
|
|
197
|
+
| Command | Description |
|
|
198
|
+
| ------------ | --------------------------- |
|
|
199
|
+
| `init` | Create `lazy.config.json` |
|
|
200
|
+
| `lock [env]` | Encrypt environment |
|
|
201
|
+
| `sync [env]` | Decrypt & merge environment |
|
|
146
202
|
|
|
147
|
-
|
|
203
|
+
### Flags
|
|
148
204
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
| Flag | Description |
|
|
206
|
+
| ---------------------- | ------------------------------------ |
|
|
207
|
+
| `-p, --profile <mode>` | Security profile (`light` / `heavy`) |
|
|
208
|
+
| `-i, --input <path>` | Input file override |
|
|
209
|
+
| `-o, --output <path>` | Output file override |
|
|
152
210
|
|
|
153
211
|
---
|
|
154
212
|
|
|
155
|
-
|
|
213
|
+
# Security Model
|
|
156
214
|
|
|
157
|
-
|
|
215
|
+
- Zero-knowledge encryption
|
|
216
|
+
- Local-only cryptography
|
|
217
|
+
- Authenticated encryption (tamper detection)
|
|
218
|
+
- No password storage
|
|
219
|
+
- No recovery backdoors
|
|
158
220
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
221
|
+
> If you lose your password, your secrets **cannot be recovered**.
|
|
222
|
+
|
|
223
|
+
This is by design.
|
|
162
224
|
|
|
163
225
|
---
|
|
164
226
|
|
|
165
|
-
|
|
227
|
+
# 🤝 Contributing
|
|
166
228
|
|
|
167
229
|
Contributions are welcome.
|
|
168
230
|
|
|
169
231
|
1. Fork the repo
|
|
170
232
|
2. Create a feature branch
|
|
171
|
-
3. Open a
|
|
233
|
+
3. Open a PR
|
|
172
234
|
|
|
173
|
-
Security
|
|
235
|
+
Security issues should be reported responsibly.
|
|
174
236
|
|
|
175
237
|
---
|
|
176
238
|
|
|
177
|
-
|
|
239
|
+
# 📄 License
|
|
178
240
|
|
|
179
241
|
MIT License © ghost
|
|
242
|
+
|
|
243
|
+
---
|
package/dist/cli/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/bin.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command4 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/cli/commands/sync.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -10,43 +10,32 @@ import inquirer from "inquirer";
|
|
|
10
10
|
|
|
11
11
|
// src/lib/storage.ts
|
|
12
12
|
import fs from "fs-extra";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var RAW_FILE = ".env";
|
|
17
|
-
|
|
18
|
-
// src/lib/storage.ts
|
|
19
|
-
async function hasRawEnv() {
|
|
20
|
-
return fs.pathExists(RAW_FILE);
|
|
21
|
-
}
|
|
22
|
-
async function hasEncEnv() {
|
|
23
|
-
return fs.pathExists(ENC_FILE);
|
|
13
|
+
import path from "path";
|
|
14
|
+
async function exists(filePath) {
|
|
15
|
+
return fs.pathExists(filePath);
|
|
24
16
|
}
|
|
25
|
-
async function
|
|
26
|
-
if (!await
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
async function readEncEnv() {
|
|
30
|
-
if (!await hasEncEnv()) {
|
|
31
|
-
throw new Error("No encrypted .env.enc file found.");
|
|
17
|
+
async function readFile(filePath) {
|
|
18
|
+
if (!await exists(filePath)) {
|
|
19
|
+
throw new Error(`File not found: ${filePath}`);
|
|
32
20
|
}
|
|
33
|
-
return fs.readFile(
|
|
34
|
-
}
|
|
35
|
-
async function writeRawEnv(content) {
|
|
36
|
-
await fs.writeFile(RAW_FILE, content, "utf-8");
|
|
21
|
+
return fs.readFile(filePath, "utf-8");
|
|
37
22
|
}
|
|
38
|
-
async function
|
|
39
|
-
await fs.writeFile(
|
|
23
|
+
async function writeFile(filePath, content) {
|
|
24
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
40
25
|
}
|
|
41
|
-
async function
|
|
26
|
+
async function addToGitIgnore(filePath) {
|
|
42
27
|
const gitignorePath = ".gitignore";
|
|
43
|
-
const
|
|
28
|
+
const filename = path.basename(filePath);
|
|
29
|
+
const ignoreRule = `
|
|
30
|
+
${filename}`;
|
|
31
|
+
if (filename.endsWith(".enc")) return;
|
|
44
32
|
if (!await fs.pathExists(gitignorePath)) {
|
|
45
|
-
await fs.writeFile(gitignorePath, ignoreRule
|
|
33
|
+
await fs.writeFile(gitignorePath, `# Added by lazy-vault${ignoreRule}
|
|
34
|
+
`);
|
|
46
35
|
return;
|
|
47
36
|
}
|
|
48
37
|
const content = await fs.readFile(gitignorePath, "utf-8");
|
|
49
|
-
if (!content.includes(
|
|
38
|
+
if (!content.includes(filename)) {
|
|
50
39
|
await fs.appendFile(gitignorePath, ignoreRule);
|
|
51
40
|
}
|
|
52
41
|
}
|
|
@@ -54,9 +43,20 @@ async function ensureGitIgnore() {
|
|
|
54
43
|
// src/lib/crypto.ts
|
|
55
44
|
import crypto from "crypto";
|
|
56
45
|
import argon2 from "argon2";
|
|
46
|
+
|
|
47
|
+
// src/constants/index.ts
|
|
57
48
|
var ALGORITHM = "aes-256-gcm";
|
|
58
|
-
var
|
|
59
|
-
|
|
49
|
+
var CONFIG_FILE = "lazy.config.json";
|
|
50
|
+
var PROFILES = {
|
|
51
|
+
// Good for CI/CD and frequent locking
|
|
52
|
+
light: { timeCost: 3, memoryCost: 64 * 1024, parallelism: 1 },
|
|
53
|
+
// Good for long-term storage or high-value keys
|
|
54
|
+
heavy: { timeCost: 10, memoryCost: 256 * 1024, parallelism: 4 }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/lib/crypto.ts
|
|
58
|
+
async function encrypt(text, password, profileName = "light") {
|
|
59
|
+
const settings = PROFILES[profileName];
|
|
60
60
|
const salt = crypto.randomBytes(16);
|
|
61
61
|
const iv = crypto.randomBytes(16);
|
|
62
62
|
const key = await argon2.hash(password, {
|
|
@@ -64,52 +64,60 @@ async function encrypt(text, password) {
|
|
|
64
64
|
salt,
|
|
65
65
|
raw: true,
|
|
66
66
|
hashLength: 32,
|
|
67
|
-
timeCost:
|
|
68
|
-
memoryCost:
|
|
69
|
-
parallelism:
|
|
67
|
+
timeCost: settings.timeCost,
|
|
68
|
+
memoryCost: settings.memoryCost,
|
|
69
|
+
parallelism: settings.parallelism
|
|
70
70
|
});
|
|
71
|
-
if (ALGORITHM !== "aes-256-gcm") {
|
|
72
|
-
throw new Error("Unsupported encryption algorithm");
|
|
73
|
-
}
|
|
74
71
|
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
75
72
|
const encrypted = Buffer.concat([
|
|
76
73
|
cipher.update(text, "utf8"),
|
|
77
74
|
cipher.final()
|
|
78
75
|
]);
|
|
79
76
|
const authTag = cipher.getAuthTag();
|
|
77
|
+
const envelope = {
|
|
78
|
+
v: 2,
|
|
79
|
+
mode: "password",
|
|
80
|
+
profile: profileName,
|
|
81
|
+
ops: {
|
|
82
|
+
mem: settings.memoryCost,
|
|
83
|
+
time: settings.timeCost,
|
|
84
|
+
parallel: settings.parallelism
|
|
85
|
+
},
|
|
86
|
+
salt: salt.toString("hex"),
|
|
87
|
+
iv: iv.toString("hex"),
|
|
88
|
+
tag: authTag.toString("hex"),
|
|
89
|
+
data: encrypted.toString("hex")
|
|
90
|
+
};
|
|
80
91
|
key.fill(0);
|
|
81
|
-
return
|
|
92
|
+
return JSON.stringify(envelope, null, 2);
|
|
82
93
|
}
|
|
83
|
-
async function decrypt(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
async function decrypt(content, password) {
|
|
95
|
+
try {
|
|
96
|
+
const envelope = JSON.parse(content);
|
|
97
|
+
const key = await argon2.hash(password, {
|
|
98
|
+
type: argon2.argon2id,
|
|
99
|
+
salt: Buffer.from(envelope.salt, "hex"),
|
|
100
|
+
raw: true,
|
|
101
|
+
hashLength: 32,
|
|
102
|
+
timeCost: envelope.ops.time,
|
|
103
|
+
memoryCost: envelope.ops.mem,
|
|
104
|
+
parallelism: envelope.ops.parallel
|
|
105
|
+
});
|
|
106
|
+
const decipher = crypto.createDecipheriv(
|
|
107
|
+
ALGORITHM,
|
|
108
|
+
key,
|
|
109
|
+
Buffer.from(envelope.iv, "hex")
|
|
110
|
+
);
|
|
111
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, "hex"));
|
|
112
|
+
const decrypted = Buffer.concat([
|
|
113
|
+
decipher.update(Buffer.from(envelope.data, "hex")),
|
|
114
|
+
decipher.final()
|
|
115
|
+
]);
|
|
116
|
+
key.fill(0);
|
|
117
|
+
return decrypted.toString("utf8");
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new Error(`Decryption failed: ${error.message}`);
|
|
91
120
|
}
|
|
92
|
-
const salt = Buffer.from(saltHex, "hex");
|
|
93
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
94
|
-
const authTag = Buffer.from(authTagHex, "hex");
|
|
95
|
-
const cipherText = Buffer.from(cipherTextHex, "hex");
|
|
96
|
-
const key = await argon2.hash(password, {
|
|
97
|
-
type: argon2.argon2id,
|
|
98
|
-
salt,
|
|
99
|
-
raw: true,
|
|
100
|
-
hashLength: 32,
|
|
101
|
-
timeCost: 3,
|
|
102
|
-
memoryCost: 65536,
|
|
103
|
-
parallelism: 1
|
|
104
|
-
});
|
|
105
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
106
|
-
decipher.setAuthTag(authTag);
|
|
107
|
-
const decrypted = Buffer.concat([
|
|
108
|
-
decipher.update(cipherText),
|
|
109
|
-
decipher.final()
|
|
110
|
-
]);
|
|
111
|
-
key.fill(0);
|
|
112
|
-
return decrypted.toString("utf8");
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
// src/lib/parser.ts
|
|
@@ -147,42 +155,92 @@ function merge(local, remote) {
|
|
|
147
155
|
return { ...local, ...remote };
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
// src/lib/config.ts
|
|
159
|
+
import fs2 from "fs-extra";
|
|
160
|
+
import "path";
|
|
161
|
+
var DEFAULTS = {
|
|
162
|
+
source: ".env",
|
|
163
|
+
output: ".env.env",
|
|
164
|
+
security: "light"
|
|
165
|
+
};
|
|
166
|
+
async function loadConfig(envName = "default") {
|
|
167
|
+
if (!await fs2.pathExists(CONFIG_FILE)) {
|
|
168
|
+
return DEFAULTS;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const raw = await fs2.readFile(CONFIG_FILE, "utf-8");
|
|
172
|
+
const config = JSON.parse(raw);
|
|
173
|
+
if (!config[envName]) {
|
|
174
|
+
if (envName === "default") return DEFAULTS;
|
|
175
|
+
throw new Error(`Environment "${envName}" not found in ${CONFIG_FILE}`);
|
|
176
|
+
}
|
|
177
|
+
return { ...DEFAULTS, ...config[envName] };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
throw new Error(`Failed to parse ${CONFIG_FILE}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function createTemplateConfig() {
|
|
183
|
+
const template = {
|
|
184
|
+
default: { source: ".env", output: ".env.enc", security: "light" },
|
|
185
|
+
production: {
|
|
186
|
+
source: ".env.prod",
|
|
187
|
+
output: ".env.prod.enc",
|
|
188
|
+
security: "heavy"
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
await fs2.writeJson(CONFIG_FILE, template, { spaces: 2 });
|
|
192
|
+
}
|
|
193
|
+
|
|
150
194
|
// src/cli/commands/sync.ts
|
|
151
|
-
var syncCommand = new Command("sync").description("Decrypts .env.enc and merges it with local .env").action(async () => {
|
|
195
|
+
var syncCommand = new Command("sync").description("Decrypts .env.enc and merges it with local .env").argument("[env]", "Environment name", "default").option("-i, --input <path>", "Override encrypted file path").option("-o, --output <path>", "Override target .env path").action(async (envName, options) => {
|
|
152
196
|
try {
|
|
153
|
-
console.log(chalk.blue("Preparing to Sync
|
|
154
|
-
|
|
155
|
-
|
|
197
|
+
console.log(chalk.blue("Preparing to Sync..."));
|
|
198
|
+
const fileConfig = await loadConfig(envName);
|
|
199
|
+
const encryptedPath = options.input || fileConfig.output;
|
|
200
|
+
const rawPath = options.output || fileConfig.source;
|
|
201
|
+
console.log(chalk.blue(` Syncing [${envName}]...`));
|
|
202
|
+
console.log(chalk.gray(` Source (Encrypted): ${encryptedPath}`));
|
|
203
|
+
console.log(chalk.gray(` Target (Raw): ${rawPath}`));
|
|
204
|
+
if (!await exists(encryptedPath)) {
|
|
205
|
+
console.error(chalk.red(`Error: File ${encryptedPath} not found.`));
|
|
156
206
|
process.exit(1);
|
|
157
207
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
208
|
+
let password = process.env.LAZY_ENV_PASSWORD;
|
|
209
|
+
if (!password) {
|
|
210
|
+
const answers = await inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: "password",
|
|
213
|
+
name: "password",
|
|
214
|
+
message: "Enter password to decrypt:",
|
|
215
|
+
mask: "*"
|
|
216
|
+
}
|
|
217
|
+
]);
|
|
218
|
+
password = answers.password;
|
|
219
|
+
} else {
|
|
220
|
+
console.log(chalk.yellow(" Using password from LAZY_ENV_PASSWORD"));
|
|
221
|
+
}
|
|
222
|
+
const encryptedContent = await readFile(encryptedPath);
|
|
168
223
|
let decryptedRaw = "";
|
|
169
224
|
try {
|
|
170
|
-
decryptedRaw = await decrypt(
|
|
225
|
+
decryptedRaw = await decrypt(
|
|
226
|
+
encryptedContent,
|
|
227
|
+
password
|
|
228
|
+
);
|
|
171
229
|
} catch (e) {
|
|
172
230
|
throw new Error("Invalid password or corrupted file.");
|
|
173
231
|
}
|
|
174
232
|
const remoteObj = parse(decryptedRaw);
|
|
175
233
|
let finalObj = remoteObj;
|
|
176
|
-
let actionMsg =
|
|
177
|
-
if (await
|
|
178
|
-
console.log(chalk.gray(
|
|
179
|
-
const localRaw = await
|
|
234
|
+
let actionMsg = `Created new ${rawPath}`;
|
|
235
|
+
if (await exists(rawPath)) {
|
|
236
|
+
console.log(chalk.gray(` Local ${rawPath} found. Merging...`));
|
|
237
|
+
const localRaw = await readFile(rawPath);
|
|
180
238
|
const localObj = parse(localRaw);
|
|
181
239
|
finalObj = merge(localObj, remoteObj);
|
|
182
240
|
actionMsg = "Merged with local .env";
|
|
183
241
|
}
|
|
184
242
|
const finalString = stringify(finalObj);
|
|
185
|
-
await
|
|
243
|
+
await writeFile(rawPath, finalString);
|
|
186
244
|
console.log(chalk.green(`
|
|
187
245
|
Success! ${actionMsg}`));
|
|
188
246
|
console.log(
|
|
@@ -195,43 +253,62 @@ Success! ${actionMsg}`));
|
|
|
195
253
|
});
|
|
196
254
|
|
|
197
255
|
// src/cli/commands/lock.ts
|
|
198
|
-
import { Command as Command2 } from "commander";
|
|
256
|
+
import { Command as Command2, Option } from "commander";
|
|
199
257
|
import chalk2 from "chalk";
|
|
200
258
|
import inquirer2 from "inquirer";
|
|
201
|
-
var lockCommand = new Command2("lock").description("Encrypts local .env file and saves it to env.enc").
|
|
259
|
+
var lockCommand = new Command2("lock").description("Encrypts local .env file and saves it to env.enc").argument("[env]", "Environment name (default, production, etc)", "default").option("-i, --input <path>", "Override input path").option("-o, --output <path>", "Override output path").addOption(
|
|
260
|
+
new Option("-p, --profile <mode>", "Override security profile").choices([
|
|
261
|
+
"light",
|
|
262
|
+
"heavy"
|
|
263
|
+
])
|
|
264
|
+
).action(async (envName, options) => {
|
|
202
265
|
try {
|
|
203
|
-
console.log(chalk2.blue("Preparing to lock
|
|
204
|
-
|
|
205
|
-
|
|
266
|
+
console.log(chalk2.blue("Preparing to lock ..."));
|
|
267
|
+
const fileConfig = await loadConfig(envName);
|
|
268
|
+
const sourcePath = options.input || fileConfig.source;
|
|
269
|
+
const outputPath = options.output || fileConfig.output;
|
|
270
|
+
const securityProfile = options.profile || fileConfig.security;
|
|
271
|
+
console.log(chalk2.blue(` Locking [${envName}]...`));
|
|
272
|
+
console.log(chalk2.gray(` Source: ${sourcePath}`));
|
|
273
|
+
console.log(chalk2.gray(` Target: ${outputPath}`));
|
|
274
|
+
console.log(chalk2.gray(` Profile: ${securityProfile}`));
|
|
275
|
+
if (!await exists(sourcePath)) {
|
|
276
|
+
console.error(chalk2.red(`Error: File ${sourcePath} not found.`));
|
|
206
277
|
process.exit(1);
|
|
207
278
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
279
|
+
let password = process.env.LAZY_ENV_PASSWORD;
|
|
280
|
+
if (!password) {
|
|
281
|
+
const answers = await inquirer2.prompt([
|
|
282
|
+
{
|
|
283
|
+
type: "password",
|
|
284
|
+
name: "password",
|
|
285
|
+
message: "Enter Password to encrypt: ",
|
|
286
|
+
mask: "*",
|
|
287
|
+
validate: (input) => input.length > 0 ? true : "Password cannot be empty."
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
type: "password",
|
|
291
|
+
name: "passwordConfirm",
|
|
292
|
+
message: "Confirm password:",
|
|
293
|
+
mask: "*"
|
|
294
|
+
}
|
|
295
|
+
]);
|
|
296
|
+
if (answers.password !== answers.passwordConfirm) {
|
|
297
|
+
console.error(chalk2.red("\n Error: Passwords do not match."));
|
|
298
|
+
process.exit(1);
|
|
222
299
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const rawContent = await
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
await ensureGitIgnore();
|
|
231
|
-
console.log(
|
|
232
|
-
chalk2.green(`
|
|
233
|
-
Success! Encrypted ${keysCount} secrets to .env.enc`)
|
|
300
|
+
password = answers.password;
|
|
301
|
+
}
|
|
302
|
+
const rawContent = await readFile(sourcePath);
|
|
303
|
+
const encryptedData = await encrypt(
|
|
304
|
+
rawContent,
|
|
305
|
+
password,
|
|
306
|
+
securityProfile
|
|
234
307
|
);
|
|
308
|
+
await writeFile(outputPath, encryptedData);
|
|
309
|
+
await addToGitIgnore(sourcePath);
|
|
310
|
+
console.log(chalk2.green(`
|
|
311
|
+
Success! Encrypted secrets to ${outputPath}`));
|
|
235
312
|
console.log(chalk2.gray("You can now commit .env.enc to Git."));
|
|
236
313
|
} catch (error) {
|
|
237
314
|
console.error(chalk2.red("\n Failed:"), error.message);
|
|
@@ -240,14 +317,32 @@ Success! Encrypted ${keysCount} secrets to .env.enc`)
|
|
|
240
317
|
});
|
|
241
318
|
|
|
242
319
|
// src/cli/bin.ts
|
|
320
|
+
import chalk4 from "chalk";
|
|
321
|
+
|
|
322
|
+
// src/cli/commands/init.ts
|
|
323
|
+
import { Command as Command3 } from "commander";
|
|
243
324
|
import chalk3 from "chalk";
|
|
244
|
-
|
|
325
|
+
import fs3 from "fs-extra";
|
|
326
|
+
var initCommand = new Command3("init").description("Creates a lazy.config.json file").action(async () => {
|
|
327
|
+
if (await fs3.pathExists(CONFIG_FILE)) {
|
|
328
|
+
console.log(chalk3.yellow("lazy.config.json already exists."));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await createTemplateConfig();
|
|
332
|
+
console.log(chalk3.green(`
|
|
333
|
+
Created ${CONFIG_FILE}`));
|
|
334
|
+
console.log(chalk3.gray('You can now run "lazy-env lock production"'));
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// src/cli/bin.ts
|
|
338
|
+
var program = new Command4();
|
|
245
339
|
program.name("lazy-vault").description("A secure, simple way to manage encrypted .env files in Git").version("1.0.0");
|
|
246
340
|
program.addCommand(syncCommand);
|
|
247
341
|
program.addCommand(lockCommand);
|
|
342
|
+
program.addCommand(initCommand);
|
|
248
343
|
program.on("command:*", () => {
|
|
249
344
|
console.error(
|
|
250
|
-
|
|
345
|
+
chalk4.red(
|
|
251
346
|
"Invalid command: %s\nSee --help for a list of available commands."
|
|
252
347
|
),
|
|
253
348
|
program.args.join(" ")
|
package/dist/cli/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/commands/sync.ts","../../src/lib/storage.ts","../../src/constants/index.ts","../../src/lib/crypto.ts","../../src/lib/parser.ts","../../src/cli/commands/lock.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport { syncCommand } from \"./commands/sync\";\r\nimport { lockCommand } from \"./commands/lock\";\r\nimport chalk from \"chalk\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"lazy-vault\")\r\n .description(\"A secure, simple way to manage encrypted .env files in Git\")\r\n .version(\"1.0.0\");\r\n\r\nprogram.addCommand(syncCommand);\r\nprogram.addCommand(lockCommand);\r\n\r\nprogram.on(\"command:*\", () => {\r\n console.error(\r\n chalk.red(\r\n \"Invalid command: %s\\nSee --help for a list of available commands.\",\r\n ),\r\n program.args.join(\" \"),\r\n );\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse(process.argv);\r\n\r\nif (!process.argv.slice(2).length) {\r\n program.outputHelp();\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as parser from \"../../lib/parser\";\r\n\r\nexport const syncCommand = new Command(\"sync\")\r\n .description(\"Decrypts .env.enc and merges it with local .env\")\r\n .action(async () => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to Sync (Decrypt)...\"));\r\n\r\n if (!(await storage.hasEncEnv())) {\r\n console.error(chalk.red(\"Error: No .env.enc file found.\"));\r\n process.exit(1);\r\n }\r\n\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter password to decrypt:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n const password = answers.password;\r\n\r\n const encryptedContent = await storage.readEncEnv();\r\n let decryptedRaw = \"\";\r\n\r\n try {\r\n decryptedRaw = await crypto.decrypt(encryptedContent, password);\r\n } catch (e) {\r\n throw new Error(\"Invalid password or corrupted file.\");\r\n }\r\n\r\n const remoteObj = parser.parse(decryptedRaw);\r\n\r\n let finalObj = remoteObj;\r\n let actionMsg = \"Created new .env\";\r\n\r\n if (await storage.hasRawEnv()) {\r\n console.log(chalk.gray(\"Local .env found. Merging...\"));\r\n const localRaw = await storage.readRawEnv();\r\n const localObj = parser.parse(localRaw);\r\n\r\n // MERGE: Remote wins conflicts, Local keeps unique keys\r\n finalObj = parser.merge(localObj, remoteObj);\r\n actionMsg = \"Merged with local .env\";\r\n }\r\n\r\n const finalString = parser.stringify(finalObj);\r\n await storage.writeRawEnv(finalString);\r\n\r\n console.log(chalk.green(`\\nSuccess! ${actionMsg}`));\r\n console.log(\r\n chalk.white(\"Keys in final .env: \") +\r\n chalk.yellow(Object.keys(finalObj).length),\r\n );\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\nFailed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import fs from \"fs-extra\";\r\nimport { ENC_FILE, RAW_FILE } from \"../constants\";\r\n\r\n/**\r\n * Checks if the raw .env file exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function hasRawEnv(): Promise<boolean> {\r\n return fs.pathExists(RAW_FILE);\r\n}\r\n\r\n/**\r\n * Checks if the encrypted .env file exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function hasEncEnv(): Promise<boolean> {\r\n return fs.pathExists(ENC_FILE);\r\n}\r\n\r\n/**\r\n * Reads the RAW .env file.\r\n * @return The content of the raw .env file\r\n */\r\nexport async function readRawEnv(): Promise<string> {\r\n if (!(await hasRawEnv())) return \"\";\r\n return fs.readFile(RAW_FILE, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Reads the ENCRYPTED .env.enc file.\r\n * @return The content of the encrypted .env.enc file\r\n */\r\nexport async function readEncEnv(): Promise<string> {\r\n if (!(await hasEncEnv())) {\r\n throw new Error(\"No encrypted .env.enc file found.\");\r\n }\r\n return fs.readFile(ENC_FILE, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the RAW .env file.\r\n * @param content The content to write to the raw .env file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeRawEnv(content: string): Promise<void> {\r\n await fs.writeFile(RAW_FILE, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the ENCRYPTED .env.enc file.\r\n * @param content The content to write to the encrypted .env.enc file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeEncEnv(content: string): Promise<void> {\r\n await fs.writeFile(ENC_FILE, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Adds .env to .gitignore if it's missing.\r\n * This is a safety feature to prevent accidental commits.\r\n */\r\nexport async function ensureGitIgnore(): Promise<void> {\r\n const gitignorePath = \".gitignore\";\r\n const ignoreRule = \"\\n# Added by lazy-vault\\n.env\\n\";\r\n\r\n if (!(await fs.pathExists(gitignorePath))) {\r\n await fs.writeFile(gitignorePath, ignoreRule);\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(gitignorePath, \"utf-8\");\r\n if (!content.includes(\".env\")) {\r\n await fs.appendFile(gitignorePath, ignoreRule);\r\n }\r\n}\r\n","export const ALGORITHM = \"aes-256-gcm\";\r\nexport const ENC_FILE = \".env.enc\";\r\nexport const RAW_FILE = \".env\";\r\n","import crypto from \"node:crypto\";\r\nimport argon2 from \"argon2\";\r\n\r\nconst ALGORITHM = \"aes-256-gcm\" as const;\r\nconst VERSION = \"v1\";\r\n\r\n/**\r\n * Encrypts a raw string using a password.\r\n * Format: salt:iv:authTag:cipherText\r\n * @param text The string to encrypt\r\n * @param password The password to use for encryption\r\n */\r\n\r\nexport async function encrypt(text: string, password: string): Promise<string> {\r\n const salt = crypto.randomBytes(16);\r\n const iv = crypto.randomBytes(16);\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: 3,\r\n memoryCost: 65536,\r\n parallelism: 1,\r\n });\r\n\r\n if (ALGORITHM !== \"aes-256-gcm\") {\r\n throw new Error(\"Unsupported encryption algorithm\");\r\n }\r\n\r\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\r\n\r\n const encrypted = Buffer.concat([\r\n cipher.update(text, \"utf8\"),\r\n cipher.final(),\r\n ]);\r\n const authTag = cipher.getAuthTag();\r\n\r\n key.fill(0);\r\n\r\n return `${VERSION}:${salt.toString(\"hex\")}:${iv.toString(\"hex\")}:${authTag.toString(\"hex\")}:${encrypted.toString(\"hex\")}`;\r\n}\r\n\r\n/** * Decrypts an encrypted string using a password.\r\n * @param encryptedText The encrypted string to decrypt\r\n * @param password The password to use for decryption\r\n * @return The decrypted string\r\n */\r\nexport async function decrypt(\r\n encryptedText: string,\r\n password: string,\r\n): Promise<string> {\r\n const parts = encryptedText.split(\":\");\r\n\r\n if (parts.length !== 5) {\r\n throw new Error(\"Invalid encrypted file format\");\r\n }\r\n const [version, saltHex, ivHex, authTagHex, cipherTextHex] = parts as [\r\n string,\r\n string,\r\n string,\r\n string,\r\n string,\r\n ];\r\n\r\n if (version !== \"v1\") {\r\n throw new Error(`Unsupported encrypted format version: ${version}`);\r\n }\r\n const salt = Buffer.from(saltHex, \"hex\");\r\n const iv = Buffer.from(ivHex, \"hex\");\r\n const authTag = Buffer.from(authTagHex, \"hex\");\r\n const cipherText = Buffer.from(cipherTextHex, \"hex\");\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: 3,\r\n memoryCost: 65536,\r\n parallelism: 1,\r\n });\r\n\r\n const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);\r\n decipher.setAuthTag(authTag);\r\n\r\n const decrypted = Buffer.concat([\r\n decipher.update(cipherText),\r\n decipher.final(),\r\n ]);\r\n\r\n key.fill(0);\r\n\r\n return decrypted.toString(\"utf8\");\r\n}\r\n","import type { EnvObject } from \"..\";\r\n\r\n/**\r\n * Parses a raw .env string into a Key-Value object.\r\n * Ignores comments and empty lines.\r\n * @param content The raw .env string\r\n * @return An object representing the parsed .env variables\r\n */\r\n\r\nexport function parse(content: string): EnvObject {\r\n const result: EnvObject = {};\r\n const lines = content.split(/\\r?\\n/);\r\n\r\n for (const line of lines) {\r\n const trimmedLine = line.trim();\r\n if (trimmedLine === \"\" || trimmedLine.startsWith(\"#\")) {\r\n continue;\r\n }\r\n\r\n if (!trimmedLine.includes(\"=\")) {\r\n throw new Error(`Invalid env line (missing '='): ${line}`);\r\n }\r\n\r\n const [rawKey, ...valueParts] = trimmedLine.split(\"=\") as [string, any];\r\n\r\n const key = rawKey.trim();\r\n if (!key) {\r\n throw new Error(`Invalid env line (empty key): ${line}`);\r\n }\r\n\r\n const value = valueParts.join(\"=\");\r\n const cleanValue = value.replace(/^['\"](.*)['\"]$/, \"$1\").trim();\r\n\r\n result[key] = cleanValue;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Stringifies a Key-Value object into a raw .env string.\r\n * @param envObject The object representing .env variables\r\n * @return A raw .env string\r\n */\r\nexport function stringify(envObject: EnvObject): string {\r\n let result = \"\";\r\n for (const [key, value] of Object.entries(envObject)) {\r\n result += `${key}=${value}\\n`;\r\n }\r\n\r\n return result.trimEnd();\r\n}\r\n\r\n/**\r\n * Merge logic\r\n * Remote keys override local keys if they exist\r\n * Unique keys from both are preserved\r\n * @param local The local .env object\r\n * @param remote The remote .env object\r\n */\r\n\r\nexport function merge(local: EnvObject, remote: EnvObject): EnvObject {\r\n return { ...local, ...remote };\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer, { type Answers } from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport { parse } from \"../../lib/parser\";\r\n\r\nexport const lockCommand = new Command(\"lock\")\r\n .description(\"Encrypts local .env file and saves it to env.enc\")\r\n .action(async () => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to lock (Encrypt)...\"));\r\n\r\n if (!(await storage.hasRawEnv())) {\r\n console.error(chalk.red(\"Error: No .env file found to encrypt.\"));\r\n process.exit(1);\r\n }\r\n\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter Password to encrypt: \",\r\n mask: \"*\",\r\n validate: (input) =>\r\n input.length > 0 ? true : \"Password cannot be empty.\",\r\n },\r\n {\r\n type: \"password\",\r\n name: \"passwordConfirm\",\r\n message: \"Confirm password:\",\r\n mask: \"*\",\r\n validate: (input: string, answers: Answers) =>\r\n input === answers.password || \"Passwords do not match.\",\r\n },\r\n ]);\r\n\r\n const password = answers.password;\r\n\r\n const rawContent = await storage.readRawEnv();\r\n const parsed = parse(rawContent);\r\n const keysCount = Object.keys(parsed).length;\r\n\r\n const encryptedData = await crypto.encrypt(rawContent, password);\r\n await storage.writeEncEnv(encryptedData);\r\n await storage.ensureGitIgnore();\r\n\r\n console.log(\r\n chalk.green(`\\nSuccess! Encrypted ${keysCount} secrets to .env.enc`),\r\n );\r\n console.log(chalk.gray(\"You can now commit .env.enc to Git.\"));\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\n Failed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n"],"mappings":"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","names":["Command","Command","chalk","inquirer","Command","chalk","inquirer","answers","chalk","Command"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/bin.ts","../../src/cli/commands/sync.ts","../../src/lib/storage.ts","../../src/lib/crypto.ts","../../src/constants/index.ts","../../src/lib/parser.ts","../../src/lib/config.ts","../../src/cli/commands/lock.ts","../../src/cli/commands/init.ts"],"sourcesContent":["#!/usr/bin/env node\r\nimport { Command } from \"commander\";\r\nimport { syncCommand } from \"./commands/sync\";\r\nimport { lockCommand } from \"./commands/lock\";\r\nimport chalk from \"chalk\";\r\nimport { initCommand } from \"./commands/init\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"lazy-vault\")\r\n .description(\"A secure, simple way to manage encrypted .env files in Git\")\r\n .version(\"1.0.0\");\r\n\r\nprogram.addCommand(syncCommand);\r\nprogram.addCommand(lockCommand);\r\nprogram.addCommand(initCommand);\r\n\r\nprogram.on(\"command:*\", () => {\r\n console.error(\r\n chalk.red(\r\n \"Invalid command: %s\\nSee --help for a list of available commands.\",\r\n ),\r\n program.args.join(\" \"),\r\n );\r\n process.exit(1);\r\n});\r\n\r\nprogram.parse(process.argv);\r\n\r\nif (!process.argv.slice(2).length) {\r\n program.outputHelp();\r\n}\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as parser from \"../../lib/parser\";\r\nimport * as config from \"../../lib/config\";\r\n\r\nexport const syncCommand = new Command(\"sync\")\r\n .description(\"Decrypts .env.enc and merges it with local .env\")\r\n .argument(\"[env]\", \"Environment name\", \"default\") // Support for 'lazy-guard sync production'\r\n .option(\"-i, --input <path>\", \"Override encrypted file path\")\r\n .option(\"-o, --output <path>\", \"Override target .env path\")\r\n .action(async (envName, options) => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to Sync...\"));\r\n\r\n const fileConfig = await config.loadConfig(envName);\r\n\r\n const encryptedPath = options.input || fileConfig.output;\r\n const rawPath = options.output || fileConfig.source;\r\n\r\n console.log(chalk.blue(` Syncing [${envName}]...`));\r\n console.log(chalk.gray(` Source (Encrypted): ${encryptedPath}`));\r\n console.log(chalk.gray(` Target (Raw): ${rawPath}`));\r\n\r\n if (!(await storage.exists(encryptedPath))) {\r\n console.error(chalk.red(`Error: File ${encryptedPath} not found.`));\r\n process.exit(1);\r\n }\r\n\r\n let password = process.env.LAZY_ENV_PASSWORD; // FOR CI/CD support.\r\n if (!password) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter password to decrypt:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n password = answers.password;\r\n } else {\r\n console.log(chalk.yellow(\" Using password from LAZY_ENV_PASSWORD\"));\r\n }\r\n\r\n const encryptedContent = await storage.readFile(encryptedPath);\r\n let decryptedRaw = \"\";\r\n\r\n try {\r\n decryptedRaw = await crypto.decrypt(\r\n encryptedContent,\r\n password as string,\r\n );\r\n } catch (e) {\r\n throw new Error(\"Invalid password or corrupted file.\");\r\n }\r\n\r\n const remoteObj = parser.parse(decryptedRaw);\r\n\r\n let finalObj = remoteObj;\r\n let actionMsg = `Created new ${rawPath}`;\r\n\r\n if (await storage.exists(rawPath)) {\r\n console.log(chalk.gray(` Local ${rawPath} found. Merging...`));\r\n const localRaw = await storage.readFile(rawPath);\r\n const localObj = parser.parse(localRaw);\r\n\r\n // MERGE: Remote wins conflicts, Local keeps unique keys\r\n finalObj = parser.merge(localObj, remoteObj);\r\n actionMsg = \"Merged with local .env\";\r\n }\r\n\r\n const finalString = parser.stringify(finalObj);\r\n await storage.writeFile(rawPath, finalString);\r\n\r\n console.log(chalk.green(`\\nSuccess! ${actionMsg}`));\r\n console.log(\r\n chalk.white(\"Keys in final .env: \") +\r\n chalk.yellow(Object.keys(finalObj).length),\r\n );\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\nFailed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\n\r\n/**\r\n * Checks if the .env files exists.\r\n * @return True if the file exists, false otherwise\r\n */\r\nexport async function exists(filePath: string): Promise<boolean> {\r\n return fs.pathExists(filePath);\r\n}\r\n\r\n/**\r\n * Reads the .env files.\r\n * @return The content of the raw .env file\r\n */\r\nexport async function readFile(filePath: string): Promise<string> {\r\n if (!(await exists(filePath))) {\r\n throw new Error(`File not found: ${filePath}`);\r\n }\r\n return fs.readFile(filePath, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Writes data to the .env files.\r\n * @param content The content to write to the raw .env file\r\n * @return A promise that resolves when the write is complete\r\n */\r\nexport async function writeFile(\r\n filePath: string,\r\n content: string,\r\n): Promise<void> {\r\n await fs.writeFile(filePath, content, \"utf-8\");\r\n}\r\n\r\n/**\r\n * Adds .env to .gitignore if it's missing.\r\n * This is a safety feature to prevent accidental commits.\r\n */\r\nexport async function addToGitIgnore(filePath: string): Promise<void> {\r\n const gitignorePath = \".gitignore\";\r\n const filename = path.basename(filePath);\r\n const ignoreRule = `\\n${filename}`;\r\n\r\n // Don't ignore encrypted files!\r\n if (filename.endsWith(\".enc\")) return;\r\n\r\n if (!(await fs.pathExists(gitignorePath))) {\r\n await fs.writeFile(gitignorePath, `# Added by lazy-vault${ignoreRule}\\n`);\r\n return;\r\n }\r\n\r\n const content = await fs.readFile(gitignorePath, \"utf-8\");\r\n if (!content.includes(filename)) {\r\n await fs.appendFile(gitignorePath, ignoreRule);\r\n }\r\n}\r\n","import crypto from \"node:crypto\";\r\nimport argon2 from \"argon2\";\r\nimport { ALGORITHM, PROFILES, VERSION } from \"../constants\";\r\nimport type { EncryptedEnvelope, ProfileName } from \"../types\";\r\n\r\n/**\r\n * Encrypts a raw string using a password.\r\n * Format: salt:iv:authTag:cipherText\r\n * @param text The string to encrypt\r\n * @param password The password to use for encryption\r\n */\r\n\r\nexport async function encrypt(\r\n text: string,\r\n password: string,\r\n profileName: ProfileName = \"light\",\r\n): Promise<string> {\r\n const settings = PROFILES[profileName];\r\n\r\n const salt = crypto.randomBytes(16);\r\n const iv = crypto.randomBytes(16);\r\n\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: salt,\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: settings.timeCost,\r\n memoryCost: settings.memoryCost,\r\n parallelism: settings.parallelism,\r\n });\r\n\r\n const cipher = crypto.createCipheriv(ALGORITHM, key, iv);\r\n\r\n const encrypted = Buffer.concat([\r\n cipher.update(text, \"utf8\"),\r\n cipher.final(),\r\n ]);\r\n const authTag = cipher.getAuthTag();\r\n\r\n const envelope: EncryptedEnvelope = {\r\n v: 2,\r\n mode: \"password\",\r\n profile: profileName,\r\n ops: {\r\n mem: settings.memoryCost,\r\n time: settings.timeCost,\r\n parallel: settings.parallelism,\r\n },\r\n salt: salt.toString(\"hex\"),\r\n iv: iv.toString(\"hex\"),\r\n tag: authTag.toString(\"hex\"),\r\n data: encrypted.toString(\"hex\"),\r\n };\r\n\r\n key.fill(0);\r\n\r\n return JSON.stringify(envelope, null, 2);\r\n}\r\n\r\n/** * Decrypts an encrypted string using a password.\r\n * @param encryptedText The encrypted string to decrypt\r\n * @param password The password to use for decryption\r\n * @return The decrypted string\r\n */\r\nexport async function decrypt(\r\n content: string,\r\n password: string,\r\n): Promise<string> {\r\n try {\r\n const envelope = JSON.parse(content) as EncryptedEnvelope;\r\n const key = await argon2.hash(password, {\r\n type: argon2.argon2id,\r\n salt: Buffer.from(envelope.salt, \"hex\"),\r\n raw: true,\r\n hashLength: 32,\r\n timeCost: envelope.ops.time,\r\n memoryCost: envelope.ops.mem,\r\n parallelism: envelope.ops.parallel,\r\n });\r\n\r\n const decipher = crypto.createDecipheriv(\r\n ALGORITHM,\r\n key,\r\n Buffer.from(envelope.iv, \"hex\"),\r\n );\r\n decipher.setAuthTag(Buffer.from(envelope.tag, \"hex\"));\r\n\r\n const decrypted = Buffer.concat([\r\n decipher.update(Buffer.from(envelope.data, \"hex\")),\r\n decipher.final(),\r\n ]);\r\n\r\n key.fill(0);\r\n\r\n return decrypted.toString(\"utf8\");\r\n } catch (error: any) {\r\n throw new Error(`Decryption failed: ${error.message}`);\r\n }\r\n}\r\n","export const ALGORITHM = \"aes-256-gcm\";\r\nexport const ENC_FILE = \".env.enc\";\r\nexport const RAW_FILE = \".env\";\r\nexport const VERSION = \"v1\";\r\nexport const CONFIG_FILE = \"lazy.config.json\";\r\n\r\nexport const PROFILES = {\r\n // Good for CI/CD and frequent locking\r\n light: { timeCost: 3, memoryCost: 64 * 1024, parallelism: 1 },\r\n // Good for long-term storage or high-value keys\r\n heavy: { timeCost: 10, memoryCost: 256 * 1024, parallelism: 4 },\r\n};\r\n","import type { EnvObject } from \"..\";\r\n\r\n/**\r\n * Parses a raw .env string into a Key-Value object.\r\n * Ignores comments and empty lines.\r\n * @param content The raw .env string\r\n * @return An object representing the parsed .env variables\r\n */\r\n\r\nexport function parse(content: string): EnvObject {\r\n const result: EnvObject = {};\r\n const lines = content.split(/\\r?\\n/);\r\n\r\n for (const line of lines) {\r\n const trimmedLine = line.trim();\r\n if (trimmedLine === \"\" || trimmedLine.startsWith(\"#\")) {\r\n continue;\r\n }\r\n\r\n if (!trimmedLine.includes(\"=\")) {\r\n throw new Error(`Invalid env line (missing '='): ${line}`);\r\n }\r\n\r\n const [rawKey, ...valueParts] = trimmedLine.split(\"=\") as [string, any];\r\n\r\n const key = rawKey.trim();\r\n if (!key) {\r\n throw new Error(`Invalid env line (empty key): ${line}`);\r\n }\r\n\r\n const value = valueParts.join(\"=\");\r\n const cleanValue = value.replace(/^['\"](.*)['\"]$/, \"$1\").trim();\r\n\r\n result[key] = cleanValue;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Stringifies a Key-Value object into a raw .env string.\r\n * @param envObject The object representing .env variables\r\n * @return A raw .env string\r\n */\r\nexport function stringify(envObject: EnvObject): string {\r\n let result = \"\";\r\n for (const [key, value] of Object.entries(envObject)) {\r\n result += `${key}=${value}\\n`;\r\n }\r\n\r\n return result.trimEnd();\r\n}\r\n\r\n/**\r\n * Merge logic\r\n * Remote keys override local keys if they exist\r\n * Unique keys from both are preserved\r\n * @param local The local .env object\r\n * @param remote The remote .env object\r\n */\r\n\r\nexport function merge(local: EnvObject, remote: EnvObject): EnvObject {\r\n return { ...local, ...remote };\r\n}\r\n","import fs from \"fs-extra\";\r\nimport path from \"path\";\r\nimport type { ConfigMap, ENVConfig } from \"../types\";\r\nimport type { EnvObject } from \"..\";\r\nimport { CONFIG_FILE } from \"../constants\";\r\n\r\nconst DEFAULTS: ENVConfig = {\r\n source: \".env\",\r\n output: \".env.env\",\r\n security: \"light\",\r\n};\r\n\r\nexport async function loadConfig(\r\n envName: string = \"default\",\r\n): Promise<ENVConfig> {\r\n if (!(await fs.pathExists(CONFIG_FILE))) {\r\n return DEFAULTS;\r\n }\r\n\r\n try {\r\n const raw = await fs.readFile(CONFIG_FILE, \"utf-8\");\r\n const config = JSON.parse(raw) as ConfigMap;\r\n\r\n if (!config[envName]) {\r\n if (envName === \"default\") return DEFAULTS;\r\n throw new Error(`Environment \"${envName}\" not found in ${CONFIG_FILE}`);\r\n }\r\n\r\n return { ...DEFAULTS, ...config[envName] };\r\n } catch (error: any) {\r\n throw new Error(`Failed to parse ${CONFIG_FILE}: ${error.message}`);\r\n }\r\n}\r\n\r\nexport async function createTemplateConfig(): Promise<void> {\r\n const template: ConfigMap = {\r\n default: { source: \".env\", output: \".env.enc\", security: \"light\" },\r\n production: {\r\n source: \".env.prod\",\r\n output: \".env.prod.enc\",\r\n security: \"heavy\",\r\n },\r\n };\r\n await fs.writeJson(CONFIG_FILE, template, { spaces: 2 });\r\n}\r\n","import { Command, Option } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport inquirer, { type Answers } from \"inquirer\";\r\n\r\nimport * as storage from \"../../lib/storage\";\r\nimport * as crypto from \"../../lib/crypto\";\r\nimport * as config from \"../../lib/config\";\r\n\r\nimport { parse } from \"../../lib/parser\";\r\nimport type { ProfileName } from \"../../types\";\r\n\r\nexport const lockCommand = new Command(\"lock\")\r\n .description(\"Encrypts local .env file and saves it to env.enc\")\r\n .argument(\"[env]\", \"Environment name (default, production, etc)\", \"default\")\r\n .option(\"-i, --input <path>\", \"Override input path\")\r\n .option(\"-o, --output <path>\", \"Override output path\")\r\n .addOption(\r\n new Option(\"-p, --profile <mode>\", \"Override security profile\").choices([\r\n \"light\",\r\n \"heavy\",\r\n ]),\r\n )\r\n .action(async (envName, options) => {\r\n try {\r\n console.log(chalk.blue(\"Preparing to lock ...\"));\r\n\r\n const fileConfig = await config.loadConfig(envName);\r\n const sourcePath = options.input || fileConfig.source;\r\n const outputPath = options.output || fileConfig.output;\r\n const securityProfile = options.profile || fileConfig.security;\r\n\r\n console.log(chalk.blue(` Locking [${envName}]...`));\r\n console.log(chalk.gray(` Source: ${sourcePath}`));\r\n console.log(chalk.gray(` Target: ${outputPath}`));\r\n console.log(chalk.gray(` Profile: ${securityProfile}`));\r\n\r\n if (!(await storage.exists(sourcePath))) {\r\n console.error(chalk.red(`Error: File ${sourcePath} not found.`));\r\n process.exit(1);\r\n }\r\n\r\n let password = process.env.LAZY_ENV_PASSWORD;\r\n if (!password) {\r\n const answers = await inquirer.prompt([\r\n {\r\n type: \"password\",\r\n name: \"password\",\r\n message: \"Enter Password to encrypt: \",\r\n mask: \"*\",\r\n validate: (input) =>\r\n input.length > 0 ? true : \"Password cannot be empty.\",\r\n },\r\n {\r\n type: \"password\",\r\n name: \"passwordConfirm\",\r\n message: \"Confirm password:\",\r\n mask: \"*\",\r\n },\r\n ]);\r\n\r\n if (answers.password !== answers.passwordConfirm) {\r\n console.error(chalk.red(\"\\n Error: Passwords do not match.\"));\r\n process.exit(1);\r\n }\r\n\r\n password = answers.password;\r\n }\r\n\r\n const rawContent = await storage.readFile(sourcePath);\r\n\r\n const encryptedData = await crypto.encrypt(\r\n rawContent,\r\n password as string,\r\n securityProfile as ProfileName,\r\n );\r\n await storage.writeFile(outputPath, encryptedData);\r\n await storage.addToGitIgnore(sourcePath);\r\n\r\n console.log(chalk.green(`\\nSuccess! Encrypted secrets to ${outputPath}`));\r\n console.log(chalk.gray(\"You can now commit .env.enc to Git.\"));\r\n } catch (error: any) {\r\n console.error(chalk.red(\"\\n Failed:\"), error.message);\r\n process.exit(1);\r\n }\r\n });\r\n","import { Command } from \"commander\";\r\nimport chalk from \"chalk\";\r\nimport * as config from \"../../lib/config\";\r\nimport fs from \"fs-extra\";\r\nimport { CONFIG_FILE } from \"../../constants\";\r\n\r\nexport const initCommand = new Command(\"init\")\r\n .description(\"Creates a lazy.config.json file\")\r\n .action(async () => {\r\n if (await fs.pathExists(CONFIG_FILE)) {\r\n console.log(chalk.yellow(\"lazy.config.json already exists.\"));\r\n return;\r\n }\r\n\r\n await config.createTemplateConfig();\r\n console.log(chalk.green(`\\nCreated ${CONFIG_FILE}`));\r\n console.log(chalk.gray('You can now run \"lazy-env lock production\"'));\r\n });\r\n"],"mappings":";;;AACA,SAAS,WAAAA,gBAAe;;;ACDxB,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,cAAc;;;ACFrB,OAAO,QAAQ;AACf,OAAO,UAAU;AAMjB,eAAsB,OAAO,UAAoC;AAC/D,SAAO,GAAG,WAAW,QAAQ;AAC/B;AAMA,eAAsB,SAAS,UAAmC;AAChE,MAAI,CAAE,MAAM,OAAO,QAAQ,GAAI;AAC7B,UAAM,IAAI,MAAM,mBAAmB,QAAQ,EAAE;AAAA,EAC/C;AACA,SAAO,GAAG,SAAS,UAAU,OAAO;AACtC;AAOA,eAAsB,UACpB,UACA,SACe;AACf,QAAM,GAAG,UAAU,UAAU,SAAS,OAAO;AAC/C;AAMA,eAAsB,eAAe,UAAiC;AACpE,QAAM,gBAAgB;AACtB,QAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,QAAM,aAAa;AAAA,EAAK,QAAQ;AAGhC,MAAI,SAAS,SAAS,MAAM,EAAG;AAE/B,MAAI,CAAE,MAAM,GAAG,WAAW,aAAa,GAAI;AACzC,UAAM,GAAG,UAAU,eAAe,wBAAwB,UAAU;AAAA,CAAI;AACxE;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,GAAG,SAAS,eAAe,OAAO;AACxD,MAAI,CAAC,QAAQ,SAAS,QAAQ,GAAG;AAC/B,UAAM,GAAG,WAAW,eAAe,UAAU;AAAA,EAC/C;AACF;;;ACvDA,OAAO,YAAY;AACnB,OAAO,YAAY;;;ACDZ,IAAM,YAAY;AAIlB,IAAM,cAAc;AAEpB,IAAM,WAAW;AAAA;AAAA,EAEtB,OAAO,EAAE,UAAU,GAAG,YAAY,KAAK,MAAM,aAAa,EAAE;AAAA;AAAA,EAE5D,OAAO,EAAE,UAAU,IAAI,YAAY,MAAM,MAAM,aAAa,EAAE;AAChE;;;ADCA,eAAsB,QACpB,MACA,UACA,cAA2B,SACV;AACjB,QAAM,WAAW,SAAS,WAAW;AAErC,QAAM,OAAO,OAAO,YAAY,EAAE;AAClC,QAAM,KAAK,OAAO,YAAY,EAAE;AAEhC,QAAM,MAAM,MAAM,OAAO,KAAK,UAAU;AAAA,IACtC,MAAM,OAAO;AAAA,IACb;AAAA,IACA,KAAK;AAAA,IACL,YAAY;AAAA,IACZ,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,IACrB,aAAa,SAAS;AAAA,EACxB,CAAC;AAED,QAAM,SAAS,OAAO,eAAe,WAAW,KAAK,EAAE;AAEvD,QAAM,YAAY,OAAO,OAAO;AAAA,IAC9B,OAAO,OAAO,MAAM,MAAM;AAAA,IAC1B,OAAO,MAAM;AAAA,EACf,CAAC;AACD,QAAM,UAAU,OAAO,WAAW;AAElC,QAAM,WAA8B;AAAA,IAClC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,SAAS;AAAA,IACT,KAAK;AAAA,MACH,KAAK,SAAS;AAAA,MACd,MAAM,SAAS;AAAA,MACf,UAAU,SAAS;AAAA,IACrB;AAAA,IACA,MAAM,KAAK,SAAS,KAAK;AAAA,IACzB,IAAI,GAAG,SAAS,KAAK;AAAA,IACrB,KAAK,QAAQ,SAAS,KAAK;AAAA,IAC3B,MAAM,UAAU,SAAS,KAAK;AAAA,EAChC;AAEA,MAAI,KAAK,CAAC;AAEV,SAAO,KAAK,UAAU,UAAU,MAAM,CAAC;AACzC;AAOA,eAAsB,QACpB,SACA,UACiB;AACjB,MAAI;AACF,UAAM,WAAW,KAAK,MAAM,OAAO;AACnC,UAAM,MAAM,MAAM,OAAO,KAAK,UAAU;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,MAAM,OAAO,KAAK,SAAS,MAAM,KAAK;AAAA,MACtC,KAAK;AAAA,MACL,YAAY;AAAA,MACZ,UAAU,SAAS,IAAI;AAAA,MACvB,YAAY,SAAS,IAAI;AAAA,MACzB,aAAa,SAAS,IAAI;AAAA,IAC5B,CAAC;AAED,UAAM,WAAW,OAAO;AAAA,MACtB;AAAA,MACA;AAAA,MACA,OAAO,KAAK,SAAS,IAAI,KAAK;AAAA,IAChC;AACA,aAAS,WAAW,OAAO,KAAK,SAAS,KAAK,KAAK,CAAC;AAEpD,UAAM,YAAY,OAAO,OAAO;AAAA,MAC9B,SAAS,OAAO,OAAO,KAAK,SAAS,MAAM,KAAK,CAAC;AAAA,MACjD,SAAS,MAAM;AAAA,IACjB,CAAC;AAED,QAAI,KAAK,CAAC;AAEV,WAAO,UAAU,SAAS,MAAM;AAAA,EAClC,SAAS,OAAY;AACnB,UAAM,IAAI,MAAM,sBAAsB,MAAM,OAAO,EAAE;AAAA,EACvD;AACF;;;AE1FO,SAAS,MAAM,SAA4B;AAChD,QAAM,SAAoB,CAAC;AAC3B,QAAM,QAAQ,QAAQ,MAAM,OAAO;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,KAAK;AAC9B,QAAI,gBAAgB,MAAM,YAAY,WAAW,GAAG,GAAG;AACrD;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,SAAS,GAAG,GAAG;AAC9B,YAAM,IAAI,MAAM,mCAAmC,IAAI,EAAE;AAAA,IAC3D;AAEA,UAAM,CAAC,QAAQ,GAAG,UAAU,IAAI,YAAY,MAAM,GAAG;AAErD,UAAM,MAAM,OAAO,KAAK;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,iCAAiC,IAAI,EAAE;AAAA,IACzD;AAEA,UAAM,QAAQ,WAAW,KAAK,GAAG;AACjC,UAAM,aAAa,MAAM,QAAQ,kBAAkB,IAAI,EAAE,KAAK;AAE9D,WAAO,GAAG,IAAI;AAAA,EAChB;AAEA,SAAO;AACT;AAOO,SAAS,UAAU,WAA8B;AACtD,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,cAAU,GAAG,GAAG,IAAI,KAAK;AAAA;AAAA,EAC3B;AAEA,SAAO,OAAO,QAAQ;AACxB;AAUO,SAAS,MAAM,OAAkB,QAA8B;AACpE,SAAO,EAAE,GAAG,OAAO,GAAG,OAAO;AAC/B;;;AC/DA,OAAOC,SAAQ;AACf,OAAiB;AAKjB,IAAM,WAAsB;AAAA,EAC1B,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AACZ;AAEA,eAAsB,WACpB,UAAkB,WACE;AACpB,MAAI,CAAE,MAAMC,IAAG,WAAW,WAAW,GAAI;AACvC,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,MAAM,MAAMA,IAAG,SAAS,aAAa,OAAO;AAClD,UAAM,SAAS,KAAK,MAAM,GAAG;AAE7B,QAAI,CAAC,OAAO,OAAO,GAAG;AACpB,UAAI,YAAY,UAAW,QAAO;AAClC,YAAM,IAAI,MAAM,gBAAgB,OAAO,kBAAkB,WAAW,EAAE;AAAA,IACxE;AAEA,WAAO,EAAE,GAAG,UAAU,GAAG,OAAO,OAAO,EAAE;AAAA,EAC3C,SAAS,OAAY;AACnB,UAAM,IAAI,MAAM,mBAAmB,WAAW,KAAK,MAAM,OAAO,EAAE;AAAA,EACpE;AACF;AAEA,eAAsB,uBAAsC;AAC1D,QAAM,WAAsB;AAAA,IAC1B,SAAS,EAAE,QAAQ,QAAQ,QAAQ,YAAY,UAAU,QAAQ;AAAA,IACjE,YAAY;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,IACZ;AAAA,EACF;AACA,QAAMA,IAAG,UAAU,aAAa,UAAU,EAAE,QAAQ,EAAE,CAAC;AACzD;;;ALnCO,IAAM,cAAc,IAAI,QAAQ,MAAM,EAC1C,YAAY,iDAAiD,EAC7D,SAAS,SAAS,oBAAoB,SAAS,EAC/C,OAAO,sBAAsB,8BAA8B,EAC3D,OAAO,uBAAuB,2BAA2B,EACzD,OAAO,OAAO,SAAS,YAAY;AAClC,MAAI;AACF,YAAQ,IAAI,MAAM,KAAK,sBAAsB,CAAC;AAE9C,UAAM,aAAa,MAAa,WAAW,OAAO;AAElD,UAAM,gBAAgB,QAAQ,SAAS,WAAW;AAClD,UAAM,UAAU,QAAQ,UAAU,WAAW;AAE7C,YAAQ,IAAI,MAAM,KAAK,eAAe,OAAO,MAAM,CAAC;AACpD,YAAQ,IAAI,MAAM,KAAK,0BAA0B,aAAa,EAAE,CAAC;AACjE,YAAQ,IAAI,MAAM,KAAK,0BAA0B,OAAO,EAAE,CAAC;AAE3D,QAAI,CAAE,MAAc,OAAO,aAAa,GAAI;AAC1C,cAAQ,MAAM,MAAM,IAAI,eAAe,aAAa,aAAa,CAAC;AAClE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,WAAW,QAAQ,IAAI;AAC3B,QAAI,CAAC,UAAU;AACb,YAAM,UAAU,MAAM,SAAS,OAAO;AAAA,QACpC;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AAED,iBAAW,QAAQ;AAAA,IACrB,OAAO;AACL,cAAQ,IAAI,MAAM,OAAO,wCAAwC,CAAC;AAAA,IACpE;AAEA,UAAM,mBAAmB,MAAc,SAAS,aAAa;AAC7D,QAAI,eAAe;AAEnB,QAAI;AACF,qBAAe,MAAa;AAAA,QAC1B;AAAA,QACA;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAEA,UAAM,YAAmB,MAAM,YAAY;AAE3C,QAAI,WAAW;AACf,QAAI,YAAY,eAAe,OAAO;AAEtC,QAAI,MAAc,OAAO,OAAO,GAAG;AACjC,cAAQ,IAAI,MAAM,KAAK,UAAU,OAAO,oBAAoB,CAAC;AAC7D,YAAM,WAAW,MAAc,SAAS,OAAO;AAC/C,YAAM,WAAkB,MAAM,QAAQ;AAGtC,iBAAkB,MAAM,UAAU,SAAS;AAC3C,kBAAY;AAAA,IACd;AAEA,UAAM,cAAqB,UAAU,QAAQ;AAC7C,UAAc,UAAU,SAAS,WAAW;AAE5C,YAAQ,IAAI,MAAM,MAAM;AAAA,WAAc,SAAS,EAAE,CAAC;AAClD,YAAQ;AAAA,MACN,MAAM,MAAM,sBAAsB,IAChC,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,MAAM;AAAA,IAC7C;AAAA,EACF,SAAS,OAAY;AACnB,YAAQ,MAAM,MAAM,IAAI,WAAW,GAAG,MAAM,OAAO;AACnD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;;;AMvFH,SAAS,WAAAC,UAAS,cAAc;AAChC,OAAOC,YAAW;AAClB,OAAOC,eAAgC;AAShC,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC1C,YAAY,kDAAkD,EAC9D,SAAS,SAAS,+CAA+C,SAAS,EAC1E,OAAO,sBAAsB,qBAAqB,EAClD,OAAO,uBAAuB,sBAAsB,EACpD;AAAA,EACC,IAAI,OAAO,wBAAwB,2BAA2B,EAAE,QAAQ;AAAA,IACtE;AAAA,IACA;AAAA,EACF,CAAC;AACH,EACC,OAAO,OAAO,SAAS,YAAY;AAClC,MAAI;AACF,YAAQ,IAAIC,OAAM,KAAK,uBAAuB,CAAC;AAE/C,UAAM,aAAa,MAAa,WAAW,OAAO;AAClD,UAAM,aAAa,QAAQ,SAAS,WAAW;AAC/C,UAAM,aAAa,QAAQ,UAAU,WAAW;AAChD,UAAM,kBAAkB,QAAQ,WAAW,WAAW;AAEtD,YAAQ,IAAIA,OAAM,KAAK,eAAe,OAAO,MAAM,CAAC;AACpD,YAAQ,IAAIA,OAAM,KAAK,cAAc,UAAU,EAAE,CAAC;AAClD,YAAQ,IAAIA,OAAM,KAAK,cAAc,UAAU,EAAE,CAAC;AAClD,YAAQ,IAAIA,OAAM,KAAK,eAAe,eAAe,EAAE,CAAC;AAExD,QAAI,CAAE,MAAc,OAAO,UAAU,GAAI;AACvC,cAAQ,MAAMA,OAAM,IAAI,eAAe,UAAU,aAAa,CAAC;AAC/D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,WAAW,QAAQ,IAAI;AAC3B,QAAI,CAAC,UAAU;AACb,YAAM,UAAU,MAAMC,UAAS,OAAO;AAAA,QACpC;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,MAAM;AAAA,UACN,UAAU,CAAC,UACT,MAAM,SAAS,IAAI,OAAO;AAAA,QAC9B;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,UACT,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AAED,UAAI,QAAQ,aAAa,QAAQ,iBAAiB;AAChD,gBAAQ,MAAMD,OAAM,IAAI,mCAAmC,CAAC;AAC5D,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAEA,iBAAW,QAAQ;AAAA,IACrB;AAEA,UAAM,aAAa,MAAc,SAAS,UAAU;AAEpD,UAAM,gBAAgB,MAAa;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAc,UAAU,YAAY,aAAa;AACjD,UAAc,eAAe,UAAU;AAEvC,YAAQ,IAAIA,OAAM,MAAM;AAAA,gCAAmC,UAAU,EAAE,CAAC;AACxE,YAAQ,IAAIA,OAAM,KAAK,qCAAqC,CAAC;AAAA,EAC/D,SAAS,OAAY;AACnB,YAAQ,MAAMA,OAAM,IAAI,YAAY,GAAG,MAAM,OAAO;AACpD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;;;APhFH,OAAOE,YAAW;;;AQJlB,SAAS,WAAAC,gBAAe;AACxB,OAAOC,YAAW;AAElB,OAAOC,SAAQ;AAGR,IAAM,cAAc,IAAIC,SAAQ,MAAM,EAC1C,YAAY,iCAAiC,EAC7C,OAAO,YAAY;AAClB,MAAI,MAAMC,IAAG,WAAW,WAAW,GAAG;AACpC,YAAQ,IAAIC,OAAM,OAAO,kCAAkC,CAAC;AAC5D;AAAA,EACF;AAEA,QAAa,qBAAqB;AAClC,UAAQ,IAAIA,OAAM,MAAM;AAAA,UAAa,WAAW,EAAE,CAAC;AACnD,UAAQ,IAAIA,OAAM,KAAK,4CAA4C,CAAC;AACtE,CAAC;;;ARVH,IAAM,UAAU,IAAIC,SAAQ;AAE5B,QACG,KAAK,YAAY,EACjB,YAAY,4DAA4D,EACxE,QAAQ,OAAO;AAElB,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,WAAW;AAC9B,QAAQ,WAAW,WAAW;AAE9B,QAAQ,GAAG,aAAa,MAAM;AAC5B,UAAQ;AAAA,IACNC,OAAM;AAAA,MACJ;AAAA,IACF;AAAA,IACA,QAAQ,KAAK,KAAK,GAAG;AAAA,EACvB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;AAED,QAAQ,MAAM,QAAQ,IAAI;AAE1B,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,EAAE,QAAQ;AACjC,UAAQ,WAAW;AACrB;","names":["Command","fs","fs","Command","chalk","inquirer","Command","chalk","inquirer","chalk","Command","chalk","fs","Command","fs","chalk","Command","chalk"]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazy-vault",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A simple CLI for encrypting and syncing .env files safely in Git",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ghost",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
-
"lazy-
|
|
9
|
+
"lazy-vault": "./dist/cli/bin.js"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=18"
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
],
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/Ghunter254/lazy-
|
|
27
|
+
"url": "https://github.com/Ghunter254/lazy-vault"
|
|
28
28
|
},
|
|
29
29
|
"bugs": {
|
|
30
|
-
"url": "https://github.com/Ghunter254/lazy-
|
|
30
|
+
"url": "https://github.com/Ghunter254/lazy-vault/issues"
|
|
31
31
|
},
|
|
32
|
-
"homepage": "https://github.com/Ghunter254/lazy-
|
|
32
|
+
"homepage": "https://github.com/Ghunter254/lazy-vault#readme",
|
|
33
33
|
"keywords": [
|
|
34
34
|
"env",
|
|
35
35
|
"dotenv",
|