Skip to content

Commit 7d3dd83

Browse files
committed
Add clear-values command and improve audit reporting
Introduces the clear-values command to remove all values from an environment file with PIN confirmation. Enhances the audit command to report keys with missing values. Updates file arrangement logic to insert blank lines between variable prefixes and improves YAML conversion to preserve order and formatting. Documentation is updated with new examples and command descriptions.
1 parent 42a446f commit 7d3dd83

7 files changed

Lines changed: 318 additions & 22 deletions

File tree

README.md

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,51 @@ envdoc create-example [file] [output]
112112
```
113113
Generates an example file with empty values based on keys in the source file.
114114

115+
**Example Output:**
116+
```env
117+
DATABASE_HOST=
118+
DATABASE_PORT=
119+
DATABASE_NAME=
120+
DATABASE_USER=
121+
DATABASE_PASSWORD=
122+
123+
API_KEY=
124+
API_SECRET=
125+
API_BASE_URL=
126+
```
127+
115128
##### Create JSON Schema
116129
```bash
117130
envdoc create-schema [file] [output]
118131
```
119132
Generates a JSON schema defining all environment variables.
120133

134+
**Example Schema:**
135+
```json
136+
{
137+
"$schema": "http://json-schema.org/draft-07/schema#",
138+
"type": "object",
139+
"properties": {
140+
"DATABASE_HOST": {
141+
"type": "string",
142+
"description": "Database Configuration"
143+
},
144+
"DATABASE_PORT": {
145+
"type": "string"
146+
},
147+
"API_KEY": {
148+
"type": "string",
149+
"description": "API Configuration"
150+
}
151+
},
152+
"required": [
153+
"DATABASE_HOST",
154+
"DATABASE_PORT",
155+
"API_KEY"
156+
]
157+
}
158+
```
159+
121160
-----------------------------------------------------------------------
122161

123162
#### 📑 File Management
@@ -126,7 +165,27 @@ Generates a JSON schema defining all environment variables.
126165
```bash
127166
envdoc arrange [file]
128167
```
129-
Sorts and groups environment variables alphabetically.
168+
Sorts and groups environment variables alphabetically with blank lines separating different prefixes.
169+
170+
**Example Output:**
171+
```env
172+
APP_DEBUG=true
173+
APP_ENV=development
174+
APP_NAME=MyApp
175+
176+
AWS_ACCESS_KEY_ID=key123
177+
AWS_SECRET_ACCESS_KEY=secret456
178+
179+
DATABASE_HOST=localhost
180+
DATABASE_PORT=5432
181+
DATABASE_NAME=myapp
182+
```
183+
184+
##### Clear Values
185+
```bash
186+
envdoc clear-values [file]
187+
```
188+
Clears all values from an environment file, leaving only the keys. This is a dangerous operation requiring PIN confirmation.
130189

131190
##### Sync
132191
```bash
@@ -142,14 +201,64 @@ Synchronizes keys across multiple files, adding missing keys with empty values.
142201
```bash
143202
envdoc audit [file]
144203
```
145-
Generates a report of duplicate keys in a file.
204+
Generates a report of duplicate keys and missing values in a file.
205+
206+
**Example Report:**
207+
```markdown
208+
# Environment Variables Audit Report
209+
210+
## Overview
211+
212+
**File:** `.env`
213+
**Total Keys:** 15
214+
**Duplicate Keys:** 1
215+
**Keys with Missing Values:** 3
216+
217+
## Duplicate Keys
218+
219+
| Key |
220+
|-----|
221+
| `API_KEY` |
222+
223+
## Keys with Missing Values
224+
225+
| Key |
226+
|-----|
227+
| `DATABASE_PASSWORD` |
228+
| `API_SECRET` |
229+
| `SMTP_PASSWORD` |
230+
```
146231

147232
##### Compare
148233
```bash
149234
envdoc compare [file1] [file2] [fileN...]
150235
```
151236
Generates a comparison report showing missing keys across files.
152237

238+
**Example Report:**
239+
```markdown
240+
# Environment Variables Comparison Report
241+
242+
## Overview
243+
244+
**Files Compared:** 3
245+
246+
## Files Analyzed
247+
248+
- `.env.development` (10 keys)
249+
- `.env.staging` (12 keys)
250+
- `.env.production` (12 keys)
251+
252+
## Missing Keys
253+
254+
### Missing in `.env.development`
255+
256+
| Key |
257+
|-----|
258+
| `SSL_CERT` |
259+
| `SSL_KEY` |
260+
```
261+
153262
##### Doctor
154263
```bash
155264
envdoc doctor

cmd/envdoc/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func init() {
4545

4646
// Utility commands
4747
rootCmd.AddCommand(commands.NewArrangeCmd())
48+
rootCmd.AddCommand(commands.NewClearValuesCmd())
4849

4950
// Info commands
5051
rootCmd.AddCommand(commands.NewVersionCmd())

internal/commands/audit.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ and duplicated keys in the specified file.`,
4949
// Find duplicates
5050
duplicates := parser.FindDuplicates(envVars)
5151

52+
// Find keys with missing values
53+
missingValues := findKeysWithMissingValues(envVars)
54+
5255
// Generate report
53-
report := generateAuditReport(inputFile, duplicates, len(envVars))
56+
report := generateAuditReport(inputFile, duplicates, missingValues, len(envVars))
5457

5558
// Show options
5659
handleReportOutput(report, "envdoc-audit")
@@ -112,18 +115,31 @@ across multiple specified files.`,
112115
}
113116
}
114117

115-
func generateAuditReport(filename string, duplicates []string, totalKeys int) string {
118+
// findKeysWithMissingValues finds keys that have empty or missing values
119+
func findKeysWithMissingValues(envVars []parser.EnvVar) []string {
120+
var missing []string
121+
for _, envVar := range envVars {
122+
if envVar.Value == "" {
123+
missing = append(missing, envVar.Key)
124+
}
125+
}
126+
return missing
127+
}
128+
129+
func generateAuditReport(filename string, duplicates []string, missingValues []string, totalKeys int) string {
116130
var sb strings.Builder
117131

118132
sb.WriteString("# Environment Variables Audit Report\n\n")
119133
sb.WriteString("## Table of Contents\n")
120134
sb.WriteString("- [Overview](#overview)\n")
121-
sb.WriteString("- [Duplicate Keys](#duplicate-keys)\n\n")
135+
sb.WriteString("- [Duplicate Keys](#duplicate-keys)\n")
136+
sb.WriteString("- [Keys with Missing Values](#keys-with-missing-values)\n\n")
122137

123138
sb.WriteString("## Overview\n\n")
124139
sb.WriteString(fmt.Sprintf("**File:** `%s`\n\n", filename))
125140
sb.WriteString(fmt.Sprintf("**Total Keys:** %d\n\n", totalKeys))
126141
sb.WriteString(fmt.Sprintf("**Duplicate Keys:** %d\n\n", len(duplicates)))
142+
sb.WriteString(fmt.Sprintf("**Keys with Missing Values:** %d\n\n", len(missingValues)))
127143

128144
sb.WriteString("## Duplicate Keys\n\n")
129145
if len(duplicates) == 0 {
@@ -137,6 +153,18 @@ func generateAuditReport(filename string, duplicates []string, totalKeys int) st
137153
sb.WriteString("\n")
138154
}
139155

156+
sb.WriteString("## Keys with Missing Values\n\n")
157+
if len(missingValues) == 0 {
158+
sb.WriteString("✓ No keys with missing values found.\n\n")
159+
} else {
160+
sb.WriteString("| Key |\n")
161+
sb.WriteString("|-----|\n")
162+
for _, key := range missingValues {
163+
sb.WriteString(fmt.Sprintf("| `%s` |\n", key))
164+
}
165+
sb.WriteString("\n")
166+
}
167+
140168
return sb.String()
141169
}
142170

internal/commands/clearvalues.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/MayR-Labs/envdoc-go/internal/parser"
8+
"github.com/MayR-Labs/envdoc-go/internal/utils"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// NewClearValuesCmd returns the clear-values command
13+
func NewClearValuesCmd() *cobra.Command {
14+
return &cobra.Command{
15+
Use: "clear-values [file]",
16+
Short: "Clear all values from an environment file",
17+
Long: `Clears all values from the specified environment file, leaving only the keys.
18+
This is a dangerous operation and requires PIN confirmation.`,
19+
Args: cobra.MaximumNArgs(1),
20+
Run: func(cmd *cobra.Command, args []string) {
21+
var inputFile string
22+
var err error
23+
24+
// Get input file
25+
if len(args) > 0 {
26+
inputFile = args[0]
27+
} else {
28+
inputFile, err = utils.PromptForEnvFile("Select the .env file to clear values:")
29+
if err != nil {
30+
fmt.Printf("Error: %v\n", err)
31+
os.Exit(1)
32+
}
33+
}
34+
35+
// Check if input file exists
36+
if !utils.FileExists(inputFile) {
37+
fmt.Printf("Error: File '%s' does not exist\n", inputFile)
38+
os.Exit(1)
39+
}
40+
41+
// First warning
42+
fmt.Printf("\n⚠️ WARNING: This operation will CLEAR ALL VALUES from '%s'\n", inputFile)
43+
fmt.Println("⚠️ This action is IRREVERSIBLE and will remove all sensitive data!")
44+
fmt.Println("⚠️ Make sure you have a backup before proceeding.")
45+
46+
// First PIN confirmation
47+
confirmed, err := utils.ConfirmWithPin("To proceed with clearing all values, please confirm with PIN")
48+
if err != nil {
49+
fmt.Printf("Error: %v\n", err)
50+
os.Exit(1)
51+
}
52+
if !confirmed {
53+
fmt.Println("Operation cancelled.")
54+
return
55+
}
56+
57+
// Second warning - final confirmation
58+
fmt.Printf("\n⚠️ FINAL WARNING: You are about to PERMANENTLY CLEAR all values in '%s'\n", inputFile)
59+
fmt.Println("⚠️ This is your LAST CHANCE to cancel this operation!")
60+
61+
// Second confirmation (yes/no)
62+
finalConfirm, err := utils.PromptForConfirmation("Are you ABSOLUTELY SURE you want to continue?")
63+
if err != nil {
64+
fmt.Printf("Error: %v\n", err)
65+
os.Exit(1)
66+
}
67+
if !finalConfirm {
68+
fmt.Println("Operation cancelled.")
69+
return
70+
}
71+
72+
// Parse input file
73+
envVars, err := parser.ParseEnvFile(inputFile)
74+
if err != nil {
75+
fmt.Printf("Error parsing file: %v\n", err)
76+
os.Exit(1)
77+
}
78+
79+
// Clear all values
80+
clearedVars := make([]parser.EnvVar, len(envVars))
81+
for i, envVar := range envVars {
82+
clearedVars[i] = parser.EnvVar{
83+
Key: envVar.Key,
84+
Value: "",
85+
Comment: envVar.Comment,
86+
BlankAfter: envVar.BlankAfter,
87+
}
88+
}
89+
90+
// Write back to file
91+
if err := parser.WriteEnvFile(inputFile, clearedVars); err != nil {
92+
fmt.Printf("Error writing file: %v\n", err)
93+
os.Exit(1)
94+
}
95+
96+
fmt.Printf("✓ All values cleared from: %s\n", inputFile)
97+
fmt.Printf("✓ %d keys retained with empty values\n", len(clearedVars))
98+
},
99+
}
100+
}

internal/commands/convert.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,15 @@ func NewToCmd() *cobra.Command {
6464
os.Exit(1)
6565
}
6666

67-
// Convert to map
68-
envMap := make(map[string]string)
69-
for _, envVar := range envVars {
70-
envMap[envVar.Key] = envVar.Value
71-
}
72-
7367
// Convert to target format
7468
var output string
7569
var ext string
7670
if format == "json" {
71+
// Convert to map for JSON (order doesn't matter for JSON display)
72+
envMap := make(map[string]string)
73+
for _, envVar := range envVars {
74+
envMap[envVar.Key] = envVar.Value
75+
}
7776
jsonData, err := json.MarshalIndent(envMap, "", " ")
7877
if err != nil {
7978
fmt.Printf("Error converting to JSON: %v\n", err)
@@ -82,12 +81,9 @@ func NewToCmd() *cobra.Command {
8281
output = string(jsonData)
8382
ext = ".json"
8483
} else {
85-
yamlData, err := yaml.Marshal(envMap)
86-
if err != nil {
87-
fmt.Printf("Error converting to YAML: %v\n", err)
88-
os.Exit(1)
89-
}
90-
output = string(yamlData)
84+
// For YAML, arrange by prefix and generate manually to preserve order
85+
arrangedVars := parser.ArrangeByPrefix(envVars)
86+
output = convertToYAMLWithBlankLines(arrangedVars)
9187
ext = ".yaml"
9288
}
9389

@@ -197,3 +193,26 @@ func NewFromCmd() *cobra.Command {
197193
},
198194
}
199195
}
196+
197+
// convertToYAMLWithBlankLines converts environment variables to YAML format with blank lines between different prefixes
198+
func convertToYAMLWithBlankLines(envVars []parser.EnvVar) string {
199+
var sb strings.Builder
200+
201+
for i, envVar := range envVars {
202+
// Escape special characters in value if needed
203+
value := envVar.Value
204+
if strings.ContainsAny(value, ":#{}[],&*!|>'\"%@`") || strings.HasPrefix(value, " ") || strings.HasSuffix(value, " ") {
205+
// Quote the value if it contains special YAML characters
206+
value = fmt.Sprintf("\"%s\"", strings.ReplaceAll(value, "\"", "\\\""))
207+
}
208+
209+
sb.WriteString(fmt.Sprintf("%s: %s\n", envVar.Key, value))
210+
211+
// Add blank line if this variable is marked for a blank line after it
212+
if envVar.BlankAfter && i < len(envVars)-1 {
213+
sb.WriteString("\n")
214+
}
215+
}
216+
217+
return sb.String()
218+
}

0 commit comments

Comments
 (0)