dotenv-diff 1.1.4 → 1.2.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/dist/cli.js +76 -22
- package/dist/lib/diffEnv.js +8 -0
- package/dist/lib/parseEnv.js +9 -0
- package/package.json +7 -2
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for the `dotenv-diff` tool.
|
|
4
|
+
*
|
|
5
|
+
* - Compares `.env` and `.env.example` files.
|
|
6
|
+
* - Optionally checks for value mismatches.
|
|
7
|
+
* - Warns about missing keys, extra keys, empty values, and mismatches.
|
|
8
|
+
*/
|
|
2
9
|
import { Command } from 'commander';
|
|
3
|
-
import path from
|
|
4
|
-
import fs from
|
|
5
|
-
import chalk from
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
import prompts from 'prompts';
|
|
14
|
+
import { parseEnvFile } from './lib/parseEnv.js';
|
|
15
|
+
import { diffEnv } from './lib/diffEnv.js';
|
|
8
16
|
const program = new Command();
|
|
9
17
|
program
|
|
10
18
|
.name('dotenv-diff')
|
|
@@ -13,51 +21,97 @@ program
|
|
|
13
21
|
.parse(process.argv);
|
|
14
22
|
const options = program.opts();
|
|
15
23
|
const checkValues = options.checkValues ?? false;
|
|
16
|
-
|
|
17
|
-
const envPath = path.resolve(
|
|
18
|
-
const examplePath = path.resolve(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const cwd = process.cwd();
|
|
25
|
+
const envPath = path.resolve(cwd, '.env');
|
|
26
|
+
const examplePath = path.resolve(cwd, '.env.example');
|
|
27
|
+
const envExists = fs.existsSync(envPath);
|
|
28
|
+
const exampleExists = fs.existsSync(examplePath);
|
|
29
|
+
// Case 1: Neither file exists
|
|
30
|
+
if (!envExists && !exampleExists) {
|
|
31
|
+
console.log(chalk.yellow('⚠️ No .env or .env.example file found. Skipping comparison.'));
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
// Case 2: .env is missing but .env.example exists
|
|
35
|
+
if (!envExists && exampleExists) {
|
|
36
|
+
console.log(chalk.yellow('📄 .env file not found.'));
|
|
37
|
+
const response = await prompts({
|
|
38
|
+
type: 'select',
|
|
39
|
+
name: 'createEnv',
|
|
40
|
+
message: '❓ Do you want to create a new .env file from .env.example?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ title: 'Yes', value: true },
|
|
43
|
+
{ title: 'No', value: false }
|
|
44
|
+
],
|
|
45
|
+
initial: 0
|
|
46
|
+
});
|
|
47
|
+
if (!response.createEnv) {
|
|
48
|
+
console.log(chalk.gray('🚫 Skipping .env creation.'));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
const exampleContent = fs.readFileSync(examplePath, 'utf-8');
|
|
52
|
+
fs.writeFileSync(envPath, exampleContent);
|
|
53
|
+
console.log(chalk.green('✅ .env file created successfully from .env.example.\n'));
|
|
54
|
+
}
|
|
55
|
+
// Case 3: .env exists, but .env.example is missing
|
|
56
|
+
if (envExists && !exampleExists) {
|
|
57
|
+
console.log(chalk.yellow('📄 .env.example file not found.'));
|
|
58
|
+
const response = await prompts({
|
|
59
|
+
type: 'select',
|
|
60
|
+
name: 'createExample',
|
|
61
|
+
message: '❓ Do you want to create a new .env.example file from .env?',
|
|
62
|
+
choices: [
|
|
63
|
+
{ title: 'Yes', value: true },
|
|
64
|
+
{ title: 'No', value: false }
|
|
65
|
+
],
|
|
66
|
+
initial: 0
|
|
67
|
+
});
|
|
68
|
+
if (!response.createExample) {
|
|
69
|
+
console.log(chalk.gray('🚫 Skipping .env.example creation.'));
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
73
|
+
fs.writeFileSync(examplePath, envContent);
|
|
74
|
+
console.log(chalk.green('✅ .env.example file created successfully from .env.\n'));
|
|
22
75
|
}
|
|
23
|
-
|
|
24
|
-
|
|
76
|
+
// Case 4: Run comparison
|
|
77
|
+
if (!fs.existsSync(envPath) || !fs.existsSync(examplePath)) {
|
|
78
|
+
console.error(chalk.red('❌ Error: .env or .env.example is missing after setup.'));
|
|
25
79
|
process.exit(1);
|
|
26
80
|
}
|
|
27
|
-
|
|
81
|
+
// Case 5: Both files exist, proceed with comparison
|
|
82
|
+
console.log(chalk.bold('🔍 Comparing .env and .env.example...\n'));
|
|
28
83
|
const current = parseEnvFile(envPath);
|
|
29
84
|
const example = parseEnvFile(examplePath);
|
|
30
85
|
const diff = diffEnv(current, example, checkValues);
|
|
31
|
-
// Find tomme variabler i .env
|
|
32
86
|
const emptyKeys = Object.entries(current)
|
|
33
|
-
.filter(([
|
|
87
|
+
.filter(([value]) => value.trim() === '')
|
|
34
88
|
.map(([key]) => key);
|
|
35
89
|
let hasWarnings = false;
|
|
36
90
|
if (diff.missing.length === 0 &&
|
|
37
91
|
diff.extra.length === 0 &&
|
|
38
92
|
emptyKeys.length === 0 &&
|
|
39
93
|
diff.valueMismatches.length === 0) {
|
|
40
|
-
console.log(chalk.green(
|
|
94
|
+
console.log(chalk.green('✅ All keys match! Your .env file is valid.'));
|
|
41
95
|
process.exit(0);
|
|
42
96
|
}
|
|
43
97
|
if (diff.missing.length > 0) {
|
|
44
|
-
console.log(chalk.red(
|
|
98
|
+
console.log(chalk.red('\n❌ Missing keys in .env:'));
|
|
45
99
|
diff.missing.forEach((key) => console.log(chalk.red(` - ${key}`)));
|
|
46
100
|
}
|
|
47
101
|
if (diff.extra.length > 0) {
|
|
48
|
-
console.log(chalk.yellow(
|
|
102
|
+
console.log(chalk.yellow('\n⚠️ Extra keys in .env (not defined in .env.example):'));
|
|
49
103
|
diff.extra.forEach((key) => console.log(chalk.yellow(` - ${key}`)));
|
|
50
104
|
}
|
|
51
105
|
if (emptyKeys.length > 0) {
|
|
52
106
|
hasWarnings = true;
|
|
53
|
-
console.log(chalk.yellow(
|
|
107
|
+
console.log(chalk.yellow('\n⚠️ The following keys in .env have no value (empty):'));
|
|
54
108
|
emptyKeys.forEach((key) => console.log(chalk.yellow(` - ${key}`)));
|
|
55
109
|
}
|
|
56
110
|
if (checkValues && diff.valueMismatches.length > 0) {
|
|
57
111
|
hasWarnings = true;
|
|
58
|
-
console.log(chalk.yellow(
|
|
112
|
+
console.log(chalk.yellow('\n⚠️ The following keys have different values:'));
|
|
59
113
|
diff.valueMismatches.forEach(({ key, expected, actual }) => {
|
|
60
114
|
console.log(chalk.yellow(` - ${key}: expected "${expected}", but got "${actual}"`));
|
|
61
115
|
});
|
|
62
116
|
}
|
|
63
|
-
process.exit(diff.missing.length > 0 ? 1 :
|
|
117
|
+
process.exit(diff.missing.length > 0 ? 1 : 0);
|
package/dist/lib/diffEnv.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two .env files and returns their differences.
|
|
3
|
+
*
|
|
4
|
+
* @param current - An object representing the current `.env` file (key-value pairs).
|
|
5
|
+
* @param example - An object representing the `.env.example` file (key-value pairs).
|
|
6
|
+
* @param checkValues - If true, compare values when the example has a non-empty value.
|
|
7
|
+
* @returns A `DiffResult` object containing missing, extra, and mismatched keys.
|
|
8
|
+
*/
|
|
1
9
|
export function diffEnv(current, example, checkValues = false) {
|
|
2
10
|
const currentKeys = Object.keys(current);
|
|
3
11
|
const exampleKeys = Object.keys(example);
|
package/dist/lib/parseEnv.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a `.env` file and returns an object with key-value pairs.
|
|
4
|
+
*
|
|
5
|
+
* @param path - The file path to the `.env` file.
|
|
6
|
+
* @returns A record object representing parsed environment variables.
|
|
7
|
+
*
|
|
8
|
+
* Lines that are empty or start with `#` (comments) are ignored.
|
|
9
|
+
* Multi-line or quoted values are not supported.
|
|
10
|
+
*/
|
|
2
11
|
export function parseEnvFile(path) {
|
|
3
12
|
const content = fs.readFileSync(path, 'utf-8');
|
|
4
13
|
const lines = content.split('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dotenv-diff",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A small CLI and library to find differences between .env and .env.example files.",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"dev": "vitest --watch",
|
|
22
22
|
"test": "vitest",
|
|
23
|
+
"lint": "eslint ./src --ext .ts",
|
|
23
24
|
"start": "node dist/cli.js"
|
|
24
25
|
},
|
|
25
26
|
"author": "Chrilleweb",
|
|
@@ -42,10 +43,14 @@
|
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"chalk": "^5.4.1",
|
|
45
|
-
"commander": "^14.0.0"
|
|
46
|
+
"commander": "^14.0.0",
|
|
47
|
+
"prompts": "^2.4.2"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@types/node": "^20.0.0",
|
|
51
|
+
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
|
52
|
+
"@typescript-eslint/parser": "^8.39.0",
|
|
53
|
+
"eslint": "^9.32.0",
|
|
49
54
|
"typescript": "^5.2.0",
|
|
50
55
|
"vitest": "^3.2.0"
|
|
51
56
|
}
|