menv-npm 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Dallas Okoye (TheDanielDallas.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Menv NPM
2
+
3
+ <!-- v0.1.0-alpha.11 -->
4
+
5
+ Managed Environment Variables for Node.js projects.
6
+
7
+ Menv is a lightweight CLI designed to bring consistency, safety, and automation to environment variable management. It ensures that your development, production, and example environment files stay in sync while proactively protecting against accidental secret leaks.
8
+
9
+ ## Features
10
+
11
+ - **Automated .env.example Generation**: Create and maintain example files that mirror your actual environment configuration without exposing sensitive values.
12
+ - **Environment Synchronization**: Detect and report inconsistencies between your local environment and documented templates.
13
+ - **CI-Ready Validation**: Validate that all required variables exist before deployment, preventing runtime crashes.
14
+ - **Real-time Watcher**: Automatically update your example files as you modify your local environment.
15
+ - **Security Scanning**: Detect potential secret leaks (AWS, Stripe, GitHub, etc.) within your environment files.
16
+ - **Auto-Protection**: Automatically ensures your environment files are added to .gitignore to prevent accidental commits.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install menv-npm --save-dev
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ Generate your initial .env.example:
27
+
28
+ ```bash
29
+ npx menv generate
30
+ ```
31
+
32
+ Check if your environment is in sync:
33
+
34
+ ```bash
35
+ npx menv sync
36
+ ```
37
+
38
+ Validate requirements for CI:
39
+
40
+ ```bash
41
+ npx menv check
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ | Command | Usage | Description |
47
+ | ---------- | ------------------- | -------------------------------------------------------------- |
48
+ | `generate` | `menv-npm generate` | Creates or updates .env.example from .env |
49
+ | `sync` | `menv-npm sync` | Compares .env and .env.example for inconsistencies |
50
+ | `check` | `menv-npm check` | Validates that all variables in the example file exist locally |
51
+ | `watch` | `menv-npm watch` | Monitors .env for changes and updates the example file |
52
+ | `doctor` | `menv-npm doctor` | Scans all environment files for potential secret leaks |
53
+
54
+ ## Community
55
+
56
+ | Documentation | Description |
57
+ | ------------------------------------- | ------------------------------------------ |
58
+ | [Contributing](CONTRIBUTING.md) | Learn how to get started with contributing |
59
+ | [Code of Conduct](CODE_OF_CONDUCT.md) | Our standards for a healthy community |
60
+ | [Security](SECURITY.md) | Procedures for reporting security issues |
61
+ | [License](LICENSE) | MIT Open Source License |
62
+
63
+ ---
64
+
65
+ <div align="center">
66
+ Managed with care by <b>Daniel Dallas Okoye</b>
67
+ </div>
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateExample } from "../envGenerator.js";
3
+ describe("envGenerator", () => {
4
+ it("should generate empty values for keys", () => {
5
+ const lines = [
6
+ { key: "PORT", value: "3000", raw: "PORT=3000" },
7
+ { key: "DB", value: "url", raw: "DB=url" },
8
+ ];
9
+ const result = generateExample(lines);
10
+ expect(result).toBe("PORT=\nDB=");
11
+ });
12
+ it("should preserve comments and empty lines", () => {
13
+ const lines = [
14
+ { key: "", value: "", raw: "# Comment" },
15
+ { key: "", value: "", raw: "" },
16
+ { key: "PORT", value: "3000", raw: "PORT=3000" },
17
+ ];
18
+ const result = generateExample(lines);
19
+ expect(result).toBe("# Comment\n\nPORT=");
20
+ });
21
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseEnv } from "../envParser.js";
3
+ describe("envParser", () => {
4
+ it("should parse simple key-value pairs", () => {
5
+ const content = "PORT=3000\nDATABASE_URL=postgres://localhost";
6
+ const result = parseEnv(content);
7
+ expect(result).toContainEqual(expect.objectContaining({ key: "PORT", value: "3000" }));
8
+ expect(result).toContainEqual(expect.objectContaining({
9
+ key: "DATABASE_URL",
10
+ value: "postgres://localhost",
11
+ }));
12
+ });
13
+ it("should handle comments and empty lines", () => {
14
+ const content = "# Comment\n\nPORT=3000";
15
+ const result = parseEnv(content);
16
+ expect(result.filter((l) => l.key)).toHaveLength(1);
17
+ expect(result[0].raw).toBe("# Comment");
18
+ expect(result[1].raw).toBe("");
19
+ });
20
+ it("should handle quoted values", () => {
21
+ const content = "SECRET=\"super secret\"\nKEY='single quoted'";
22
+ const result = parseEnv(content);
23
+ expect(result).toContainEqual(expect.objectContaining({ key: "SECRET", value: "super secret" }));
24
+ expect(result).toContainEqual(expect.objectContaining({ key: "KEY", value: "single quoted" }));
25
+ });
26
+ });
@@ -0,0 +1,6 @@
1
+ import { EnvLine } from "./envParser.js";
2
+ /**
3
+ * Generates .env.example content from parsed EnvLines.
4
+ * Values are stripped, but comments and structure are preserved.
5
+ */
6
+ export declare function generateExample(lines: EnvLine[]): string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Generates .env.example content from parsed EnvLines.
3
+ * Values are stripped, but comments and structure are preserved.
4
+ */
5
+ export function generateExample(lines) {
6
+ return lines
7
+ .map((line) => {
8
+ if (!line.key)
9
+ return line.raw;
10
+ return `${line.key}=`;
11
+ })
12
+ .join("\n");
13
+ }
@@ -0,0 +1,11 @@
1
+ export interface EnvLine {
2
+ key: string;
3
+ value: string;
4
+ comment?: string;
5
+ raw: string;
6
+ }
7
+ /**
8
+ * Parses raw .env content into structured data.
9
+ * Handles quoted values, escaped characters, and inline comments.
10
+ */
11
+ export declare function parseEnv(content: string): EnvLine[];
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Parses raw .env content into structured data.
3
+ * Handles quoted values, escaped characters, and inline comments.
4
+ */
5
+ export function parseEnv(content) {
6
+ const lines = [];
7
+ const rawLines = content.split(/\r?\n/);
8
+ for (const raw of rawLines) {
9
+ const trimmed = raw.trim();
10
+ // Skip empty lines or pure comment lines
11
+ if (!trimmed || trimmed.startsWith("#")) {
12
+ lines.push({ key: "", value: "", raw });
13
+ continue;
14
+ }
15
+ const match = trimmed.match(/^([^=:#]+)[=:] ?(.*)/);
16
+ if (!match) {
17
+ lines.push({ key: "", value: "", raw });
18
+ continue;
19
+ }
20
+ let [_, key, value] = match;
21
+ key = key.trim();
22
+ value = value.trim();
23
+ // Remove surrounding quotes if present
24
+ if ((value.startsWith('"') && value.endsWith('"')) ||
25
+ (value.startsWith("'") && value.endsWith("'"))) {
26
+ value = value.slice(1, -1);
27
+ }
28
+ lines.push({ key, value, raw });
29
+ }
30
+ return lines;
31
+ }
@@ -0,0 +1,9 @@
1
+ import { EnvLine } from "./envParser.js";
2
+ export interface SyncResult {
3
+ missingInExample: string[];
4
+ missingInEnv: string[];
5
+ }
6
+ /**
7
+ * Compares keys between .env and .env.example.
8
+ */
9
+ export declare function compareEnvKeys(envLines: EnvLine[], exampleLines: EnvLine[]): SyncResult;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Compares keys between .env and .env.example.
3
+ */
4
+ export function compareEnvKeys(envLines, exampleLines) {
5
+ const envKeys = new Set(envLines.map((l) => l.key).filter(Boolean));
6
+ const exampleKeys = new Set(exampleLines.map((l) => l.key).filter(Boolean));
7
+ return {
8
+ missingInExample: Array.from(envKeys).filter((k) => !exampleKeys.has(k)),
9
+ missingInEnv: Array.from(exampleKeys).filter((k) => !envKeys.has(k)),
10
+ };
11
+ }
@@ -0,0 +1,5 @@
1
+ import chokidar from "chokidar";
2
+ /**
3
+ * Watches for changes in .env and regenerates .env.example.
4
+ */
5
+ export declare function startWatcher(): Promise<chokidar.FSWatcher>;
@@ -0,0 +1,32 @@
1
+ import chokidar from "chokidar";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import pc from "picocolors";
5
+ import { parseEnv } from "./envParser.js";
6
+ import { generateExample } from "./envGenerator.js";
7
+ /**
8
+ * Watches for changes in .env and regenerates .env.example.
9
+ */
10
+ export async function startWatcher() {
11
+ const envPath = path.resolve(process.cwd(), ".env");
12
+ const examplePath = path.resolve(process.cwd(), ".env.example");
13
+ console.log(pc.dim(`Watching for changes in ${pc.cyan(".env")}...`));
14
+ const watcher = chokidar.watch(envPath, {
15
+ persistent: true,
16
+ ignoreInitial: true,
17
+ });
18
+ watcher.on("change", async () => {
19
+ try {
20
+ console.log(pc.blue("⚡ .env changed, regenerating .env.example..."));
21
+ const content = await fs.readFile(envPath, "utf8");
22
+ const parsed = parseEnv(content);
23
+ const generated = generateExample(parsed);
24
+ await fs.writeFile(examplePath, generated);
25
+ console.log(pc.green("✔ .env.example updated."));
26
+ }
27
+ catch (error) {
28
+ console.error(pc.red("✖ Failed to update .env.example in watch mode:"), error);
29
+ }
30
+ });
31
+ return watcher;
32
+ }
@@ -0,0 +1,10 @@
1
+ import { EnvLine } from "./envParser.js";
2
+ export interface SecretIssue {
3
+ key: string;
4
+ type: string;
5
+ line: number;
6
+ }
7
+ /**
8
+ * Scans environment lines for potential secret leaks.
9
+ */
10
+ export declare function scanSecrets(lines: EnvLine[]): SecretIssue[];
@@ -0,0 +1,26 @@
1
+ const PATTERNS = [
2
+ { name: "AWS Access Key", regex: /AKIA[0-9A-Z]{16}/ },
3
+ { name: "Stripe Secret Key", regex: /sk_live_[0-9a-zA-Z]{24}/ },
4
+ { name: "GitHub Token", regex: /ghp_[0-9a-zA-Z]{36}/ },
5
+ { name: "Private Key", regex: /-----BEGIN [A-Z ]+ PRIVATE KEY-----/ },
6
+ ];
7
+ /**
8
+ * Scans environment lines for potential secret leaks.
9
+ */
10
+ export function scanSecrets(lines) {
11
+ const issues = [];
12
+ lines.forEach((line, index) => {
13
+ if (!line.key)
14
+ return;
15
+ for (const pattern of PATTERNS) {
16
+ if (pattern.regex.test(line.value) || pattern.regex.test(line.raw)) {
17
+ issues.push({
18
+ key: line.key,
19
+ type: pattern.name,
20
+ line: index + 1,
21
+ });
22
+ }
23
+ }
24
+ });
25
+ return issues;
26
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ import { cac } from "cac";
3
+ import pc from "picocolors";
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { parseEnv } from "./core/envParser.js";
7
+ import { generateExample } from "./core/envGenerator.js";
8
+ import { compareEnvKeys } from "./core/envValidator.js";
9
+ import { startWatcher } from "./core/envWatcher.js";
10
+ import { scanSecrets } from "./core/secretScanner.js";
11
+ import { ensureGitignore } from "./utils/git.js";
12
+ import { findTemplateFile } from "./utils/template.js";
13
+ const cli = cac("menv");
14
+ cli.command("generate", "Generate .env.example from .env").action(async () => {
15
+ try {
16
+ await ensureGitignore();
17
+ const envPath = path.resolve(process.cwd(), ".env");
18
+ const examplePath = path.resolve(process.cwd(), ".env.example");
19
+ if (!(await fs.stat(envPath).catch(() => false))) {
20
+ console.error(pc.red("✖ .env file not found in current directory."));
21
+ process.exit(1);
22
+ }
23
+ const content = await fs.readFile(envPath, "utf8");
24
+ const parsed = parseEnv(content);
25
+ const generated = generateExample(parsed);
26
+ await fs.writeFile(examplePath, generated);
27
+ console.log(pc.green("✔ .env.example generated successfully."));
28
+ }
29
+ catch (error) {
30
+ console.error(pc.red("✖ Failed to generate .env.example:"), error);
31
+ process.exit(1);
32
+ }
33
+ });
34
+ cli
35
+ .command("sync", "Check for inconsistencies between .env and .env.example")
36
+ .alias("synch")
37
+ .action(async () => {
38
+ try {
39
+ await ensureGitignore();
40
+ const envPath = path.resolve(process.cwd(), ".env");
41
+ const templatePath = await findTemplateFile();
42
+ if (!(await fs.stat(envPath).catch(() => false))) {
43
+ console.error(pc.red("✖ .env file not found."));
44
+ process.exit(1);
45
+ }
46
+ if (!templatePath) {
47
+ console.error(pc.red("✖ No environment template (.env.example or .env.sample) found."));
48
+ process.exit(1);
49
+ }
50
+ const templateName = path.basename(templatePath);
51
+ console.log(pc.dim(`\nComparing .env with ${templateName}...`));
52
+ const envContent = await fs.readFile(envPath, "utf8");
53
+ const templateContent = await fs.readFile(templatePath, "utf8");
54
+ const envParsed = parseEnv(envContent);
55
+ const templateParsed = parseEnv(templateContent);
56
+ const { missingInExample: missingInTemplate, missingInEnv } = compareEnvKeys(envParsed, templateParsed);
57
+ if (missingInTemplate.length === 0 && missingInEnv.length === 0) {
58
+ console.log(pc.green(`✔ ${templateName} is in sync with .env`));
59
+ return;
60
+ }
61
+ if (missingInTemplate.length > 0) {
62
+ console.log(pc.yellow(`\nMissing in ${templateName}:`));
63
+ missingInTemplate.forEach((k) => console.log(pc.dim(` - ${k}`)));
64
+ }
65
+ if (missingInEnv.length > 0) {
66
+ console.log(pc.yellow(`\nExtra in ${templateName} (not in .env):`));
67
+ missingInEnv.forEach((k) => console.log(pc.dim(` - ${k}`)));
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.error(pc.red("✖ Failed to sync environment files:"), error);
72
+ process.exit(1);
73
+ }
74
+ });
75
+ cli
76
+ .command("check", "Validate that all required variables in .env.example exist in .env")
77
+ .action(async () => {
78
+ try {
79
+ await ensureGitignore();
80
+ const envPath = path.resolve(process.cwd(), ".env");
81
+ const templatePath = await findTemplateFile();
82
+ if (!templatePath) {
83
+ console.error(pc.red("✖ No environment template (.env.example or .env.sample) found."));
84
+ process.exit(1);
85
+ }
86
+ const templateName = path.basename(templatePath);
87
+ console.log(pc.dim(`\nValidating .env against ${templateName}...`));
88
+ const envContent = (await fs.stat(envPath).catch(() => false))
89
+ ? await fs.readFile(envPath, "utf8")
90
+ : "";
91
+ const templateContent = await fs.readFile(templatePath, "utf8");
92
+ const envParsed = parseEnv(envContent);
93
+ const exampleParsed = parseEnv(templateContent);
94
+ const envKeys = new Set(envParsed.map((l) => l.key).filter(Boolean));
95
+ const requiredKeys = exampleParsed.map((l) => l.key).filter(Boolean);
96
+ let hasMissing = false;
97
+ console.log(pc.bold("\nEnvironment Variable Check:"));
98
+ for (const key of requiredKeys) {
99
+ if (envKeys.has(key)) {
100
+ console.log(`${pc.green("✔")} ${key}`);
101
+ }
102
+ else {
103
+ console.log(`${pc.red("✖")} ${key} (missing)`);
104
+ hasMissing = true;
105
+ }
106
+ }
107
+ if (hasMissing) {
108
+ console.error(pc.red(`\n✖ Environment validation failed. Please check ${templateName}.`));
109
+ process.exit(1);
110
+ }
111
+ else {
112
+ console.log(pc.green("\n✔ Environment validation passed."));
113
+ }
114
+ }
115
+ catch (error) {
116
+ console.error(pc.red("✖ Validation error:"), error);
117
+ process.exit(1);
118
+ }
119
+ });
120
+ cli
121
+ .command("watch", "Watch for .env changes and automatically update .env.example")
122
+ .action(async () => {
123
+ try {
124
+ await ensureGitignore();
125
+ const envPath = path.resolve(process.cwd(), ".env");
126
+ if (!(await fs.stat(envPath).catch(() => false))) {
127
+ console.error(pc.red("✖ .env file not found."));
128
+ process.exit(1);
129
+ }
130
+ await startWatcher();
131
+ }
132
+ catch (error) {
133
+ console.error(pc.red("✖ Watcher error:"), error);
134
+ process.exit(1);
135
+ }
136
+ });
137
+ cli
138
+ .command("doctor", "Scan for potential secret leaks in environment files")
139
+ .action(async () => {
140
+ try {
141
+ await ensureGitignore();
142
+ const files = [
143
+ ".env",
144
+ ".env.example",
145
+ ".env.local",
146
+ ".env.development",
147
+ ".env.production",
148
+ ];
149
+ let totalIssues = 0;
150
+ for (const file of files) {
151
+ const filePath = path.resolve(process.cwd(), file);
152
+ if (!(await fs.stat(filePath).catch(() => false)))
153
+ continue;
154
+ const content = await fs.readFile(filePath, "utf8");
155
+ const parsed = parseEnv(content);
156
+ const issues = scanSecrets(parsed);
157
+ if (issues.length > 0) {
158
+ console.log(pc.yellow(`\n⚠ Potential secrets found in ${pc.bold(file)}:`));
159
+ issues.forEach((issue) => {
160
+ console.log(`${pc.dim(`Line ${issue.line}:`)} ${pc.red(issue.key)} (${issue.type})`);
161
+ });
162
+ totalIssues += issues.length;
163
+ }
164
+ }
165
+ if (totalIssues === 0) {
166
+ console.log(pc.green("✔ No potential secrets detected."));
167
+ }
168
+ else {
169
+ console.log(pc.red(`\n✖ Total issues found: ${totalIssues}`));
170
+ }
171
+ }
172
+ catch (error) {
173
+ console.error(pc.red("✖ Doctor error:"), error);
174
+ process.exit(1);
175
+ }
176
+ });
177
+ cli.help();
178
+ cli.version("0.1.0");
179
+ cli.parse();
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Ensures that .env files are present in .gitignore.
3
+ * Creates .gitignore if it doesn't exist.
4
+ */
5
+ export declare function ensureGitignore(): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import pc from "picocolors";
4
+ const ENV_PATTERNS = [
5
+ ".env",
6
+ ".env.local",
7
+ ".env.development",
8
+ ".env.production",
9
+ ".env.test",
10
+ "node_modules",
11
+ "dist",
12
+ "!.env.example",
13
+ "!.env.sample",
14
+ ];
15
+ const SECTION_HEADER = "# Menv: Environment file protection";
16
+ /**
17
+ * Ensures that .env files are present in .gitignore.
18
+ * Creates .gitignore if it doesn't exist.
19
+ */
20
+ export async function ensureGitignore() {
21
+ const gitignorePath = path.resolve(process.cwd(), ".gitignore");
22
+ try {
23
+ let content = "";
24
+ let exists = false;
25
+ try {
26
+ content = await fs.readFile(gitignorePath, "utf8");
27
+ exists = true;
28
+ }
29
+ catch {
30
+ // File doesn't exist, we'll create it
31
+ }
32
+ const lines = content.split("\n").map((l) => l.trim());
33
+ const missingPatterns = ENV_PATTERNS.filter((p) => !lines.includes(p));
34
+ if (missingPatterns.length === 0) {
35
+ return;
36
+ }
37
+ console.log(pc.dim("Updating .gitignore to protect environment files..."));
38
+ let newContent = content;
39
+ if (exists && content.length > 0 && !content.endsWith("\n")) {
40
+ newContent += "\n";
41
+ }
42
+ if (!content.includes(SECTION_HEADER)) {
43
+ newContent += `\n${SECTION_HEADER}\n`;
44
+ }
45
+ for (const pattern of missingPatterns) {
46
+ newContent += `${pattern}\n`;
47
+ }
48
+ await fs.writeFile(gitignorePath, newContent);
49
+ }
50
+ catch (error) {
51
+ // We don't want to crash the tool if .gitignore update fails,
52
+ // but we should warn the user.
53
+ console.warn(pc.yellow("⚠ Warning: Could not update .gitignore. Please ensure your .env files are ignored manually."));
54
+ }
55
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Finds the environment template file in the current directory.
3
+ * Returns the path to .env.example or .env.sample if found.
4
+ */
5
+ export declare function findTemplateFile(): Promise<string | null>;
@@ -0,0 +1,16 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const TEMPLATE_VARIANTS = [".env.example", ".env.sample"];
4
+ /**
5
+ * Finds the environment template file in the current directory.
6
+ * Returns the path to .env.example or .env.sample if found.
7
+ */
8
+ export async function findTemplateFile() {
9
+ for (const variant of TEMPLATE_VARIANTS) {
10
+ const filePath = path.resolve(process.cwd(), variant);
11
+ if (await fs.stat(filePath).catch(() => false)) {
12
+ return filePath;
13
+ }
14
+ }
15
+ return null;
16
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "menv-npm",
3
+ "version": "0.1.1",
4
+ "description": "Managed environment variables for Node.js",
5
+ "type": "module",
6
+ "bin": {
7
+ "menv": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "lint": "eslint src/**/*.ts",
16
+ "test": "vitest run"
17
+ },
18
+ "keywords": [
19
+ "env",
20
+ "dotenv",
21
+ "configuration",
22
+ "validator",
23
+ "security"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "cac": "^6.7.14",
29
+ "picocolors": "^1.0.0",
30
+ "chokidar": "^3.6.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.12.7",
34
+ "typescript": "^5.4.5",
35
+ "vitest": "^1.6.0"
36
+ }
37
+ }