@claude-agent/envcheck 1.4.0 → 1.5.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/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@claude-agent/envcheck.svg)](https://www.npmjs.com/package/@claude-agent/envcheck)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- > Validate .env files, compare with .env.example, find missing or empty variables.
7
+ > Validate .env files, compare with .env.example, find missing or empty variables. **Now with monorepo support!**
8
8
 
9
- Never deploy with missing environment variables again.
9
+ Never deploy with missing environment variables again. Works across entire monorepos with a single command.
10
10
 
11
11
  **Built autonomously by [Claude](https://claude.ai)** - an AI assistant by Anthropic.
12
12
 
@@ -34,6 +34,9 @@ envcheck .env.production
34
34
  # Require specific variables
35
35
  envcheck -r "DATABASE_URL,API_KEY"
36
36
 
37
+ # Scan entire monorepo
38
+ envcheck monorepo
39
+
37
40
  # Compare two files
38
41
  envcheck compare .env .env.staging
39
42
 
@@ -101,6 +104,104 @@ envcheck list .env -j
101
104
  envcheck get .env DATABASE_URL
102
105
  ```
103
106
 
107
+ ### Monorepo Command
108
+
109
+ Scan all apps and packages in a monorepo with a single command:
110
+
111
+ ```bash
112
+ # Scan from current directory
113
+ envcheck monorepo
114
+
115
+ # Scan specific directory
116
+ envcheck monorepo ./my-monorepo
117
+
118
+ # With verbose output (shows all issues per app)
119
+ envcheck monorepo --verbose
120
+
121
+ # JSON output for CI/CD
122
+ envcheck monorepo --json
123
+
124
+ # Enable secret detection
125
+ envcheck monorepo --secrets
126
+ ```
127
+
128
+ ## Monorepo Support
129
+
130
+ envcheck can scan entire monorepos, validating environment variables across all apps and packages.
131
+
132
+ ### Supported Structures
133
+
134
+ envcheck automatically detects these common monorepo patterns:
135
+
136
+ ```
137
+ my-monorepo/
138
+ ├── apps/
139
+ │ ├── web/ # ✓ Scanned
140
+ │ │ ├── .env
141
+ │ │ └── .env.example
142
+ │ └── api/ # ✓ Scanned
143
+ │ ├── .env
144
+ │ └── .env.example
145
+ ├── packages/
146
+ │ ├── shared/ # ○ Skipped (no .env.example)
147
+ │ └── utils/ # ✓ Scanned
148
+ │ ├── .env
149
+ │ └── .env.example
150
+ └── .env.example # ✓ Root included if exists
151
+ ```
152
+
153
+ Supported directories: `apps/`, `packages/`, `workspaces/`, `services/`, `libs/`
154
+
155
+ ### Example Output
156
+
157
+ ```
158
+ $ envcheck monorepo
159
+
160
+ Monorepo Environment Check
161
+ Root: /path/to/monorepo
162
+
163
+ ✓ apps/web: passed
164
+ ✗ apps/api: 1 error(s)
165
+ ○ packages/shared: skipped (No .env.example found)
166
+ ✓ packages/utils: passed
167
+
168
+ Summary: 4 apps scanned
169
+ ✓ 2 passed
170
+ ✗ 1 failed
171
+ ○ 1 skipped
172
+
173
+ ✗ 1 error(s), 0 warning(s)
174
+ ```
175
+
176
+ ### Consistency Checks
177
+
178
+ envcheck can detect inconsistencies across apps:
179
+
180
+ - **Shared variables** - Track which variables appear in multiple apps
181
+ - **Type mismatches** - Detect when the same variable has different type hints in different apps
182
+
183
+ ```javascript
184
+ const { scanMonorepo } = require('@claude-agent/envcheck');
185
+
186
+ const result = scanMonorepo('.', { checkConsistency: true });
187
+
188
+ console.log(result.consistency.sharedVars);
189
+ // { API_URL: ['apps/web', 'apps/api'] }
190
+
191
+ console.log(result.consistency.mismatches);
192
+ // [{ variable: 'API_URL', issue: 'type_mismatch', details: [...] }]
193
+ ```
194
+
195
+ ### GitHub Action for Monorepos
196
+
197
+ ```yaml
198
+ - name: Validate all environment files
199
+ uses: claude-agent-tools/envcheck@v1
200
+ with:
201
+ monorepo: 'true'
202
+ strict: 'true'
203
+ ```
204
+
104
205
  ## API Usage
105
206
 
106
207
  ```javascript
@@ -357,22 +458,24 @@ WITH_EQUALS=postgres://user:pass@host/db?opt=val
357
458
  - **Auto-detection** - Finds .env.example automatically
358
459
  - **CI-friendly** - Exit codes and JSON output
359
460
  - **Comprehensive** - Parse, validate, compare, generate
360
- - **Well-tested** - 72 tests covering edge cases
361
-
362
- ## vs. dotenv-safe / envalid
363
-
364
- | Feature | envcheck | dotenv-safe | envalid |
365
- |---------|----------|-------------|---------|
366
- | Validates presence | ✅ | ✅ | ✅ |
367
- | Based on .env.example | ✅ | ✅ | ❌ (schema) |
368
- | **Static validation** | ✅ | ❌ | ❌ |
369
- | **CI/CD integration** | ✅ GitHub Action | ❌ | ❌ |
370
- | **Pre-commit hook** | ✅ | ❌ | ❌ |
371
- | Type validation | ✅ (static) | ❌ | (runtime) |
372
- | **Secret detection** | ✅ | ❌ | ❌ |
373
- | Zero dependencies | ✅ | ❌ | ❌ |
374
-
375
- **Key difference:** envcheck validates *before* deployment (shift-left), while dotenv-safe and envalid validate at runtime when your app starts. Catch missing env vars and type errors in CI, not in production.
461
+ - **Monorepo support** - Scan all apps/packages in one command
462
+ - **Well-tested** - 87 tests covering edge cases
463
+
464
+ ## vs. dotenv-safe / envalid / dotenv-mono
465
+
466
+ | Feature | envcheck | dotenv-safe | envalid | dotenv-mono |
467
+ |---------|----------|-------------|---------|-------------|
468
+ | Validates presence | | ✅ | ✅ | ❌ |
469
+ | Based on .env.example | ✅ | ✅ | (schema) | ❌ |
470
+ | **Static validation** | ✅ | | ❌ | ❌ |
471
+ | **CI/CD integration** | ✅ GitHub Action | ❌ | ❌ | ❌ |
472
+ | **Pre-commit hook** | ✅ | | ❌ | |
473
+ | Type validation | ✅ (static) | ❌ | ✅ (runtime) | ❌ |
474
+ | **Secret detection** | ✅ | ❌ | ❌ | ❌ |
475
+ | **Monorepo scan** | ✅ | ❌ | ❌ | ❌ |
476
+ | Zero dependencies | | | | |
477
+
478
+ **Key difference:** envcheck validates *before* deployment (shift-left), while dotenv-safe and envalid validate at runtime when your app starts. dotenv-mono helps load env vars in monorepos but doesn't validate them. envcheck is the only tool that validates across entire monorepos with a single command.
376
479
 
377
480
  ## License
378
481
 
package/bin/cli.js CHANGED
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { check, compare, validate, list, get, readEnvFile } = require('../src/index.js');
4
+ const { check, compare, validate, list, get, readEnvFile, scanMonorepo, formatMonorepoResult } = require('../src/index.js');
5
5
  const path = require('path');
6
6
  const fs = require('fs');
7
7
 
8
- const VERSION = '1.0.0';
8
+ const VERSION = '1.5.0';
9
9
 
10
10
  const HELP = `
11
11
  envcheck - Validate .env files
12
12
 
13
13
  USAGE
14
14
  envcheck [options] [file]
15
+ envcheck monorepo [directory]
15
16
  envcheck compare <env> <example>
16
17
 
17
18
  COMMANDS
18
19
  check (default) Check .env file, optionally against .env.example
20
+ monorepo Scan all apps/packages in a monorepo
19
21
  compare Compare two env files
20
22
  list List variables in a file
21
23
  get <key> Get a specific variable value
@@ -26,6 +28,8 @@ OPTIONS
26
28
  --no-empty Warn on empty values
27
29
  --no-extra Error on variables not in example
28
30
  --strict Treat warnings as errors
31
+ --secrets Enable secret detection (warn about real secrets)
32
+ --verbose Show detailed output (monorepo mode)
29
33
  -q, --quiet Only output errors
30
34
  -j, --json Output as JSON
31
35
  -v, --version Show version
@@ -35,6 +39,9 @@ EXAMPLES
35
39
  envcheck Check .env against .env.example
36
40
  envcheck .env.production Check specific file
37
41
  envcheck -r "API_KEY,DB_URL" Require specific variables
42
+ envcheck monorepo Scan monorepo from current directory
43
+ envcheck monorepo ./my-monorepo Scan specific directory
44
+ envcheck monorepo --verbose Show all issues per app
38
45
  envcheck compare .env .env.prod Compare two files
39
46
  envcheck list .env List all variables
40
47
  envcheck get .env API_KEY Get specific value
@@ -51,6 +58,8 @@ function parseArgs(args) {
51
58
  strict: false,
52
59
  quiet: false,
53
60
  json: false,
61
+ verbose: false,
62
+ detectSecrets: false,
54
63
  args: []
55
64
  };
56
65
 
@@ -78,11 +87,15 @@ function parseArgs(args) {
78
87
  result.noExtra = true;
79
88
  } else if (arg === '--strict') {
80
89
  result.strict = true;
90
+ } else if (arg === '--secrets') {
91
+ result.detectSecrets = true;
92
+ } else if (arg === '--verbose') {
93
+ result.verbose = true;
81
94
  } else if (arg === '-q' || arg === '--quiet') {
82
95
  result.quiet = true;
83
96
  } else if (arg === '-j' || arg === '--json') {
84
97
  result.json = true;
85
- } else if (arg === 'compare' || arg === 'list' || arg === 'get') {
98
+ } else if (arg === 'compare' || arg === 'list' || arg === 'get' || arg === 'monorepo') {
86
99
  result.command = arg;
87
100
  } else if (!arg.startsWith('-')) {
88
101
  result.args.push(arg);
@@ -277,6 +290,32 @@ function runGet(opts) {
277
290
  return 0;
278
291
  }
279
292
 
293
+ function runMonorepo(opts) {
294
+ const rootDir = opts.args[0] || '.';
295
+
296
+ const result = scanMonorepo(rootDir, {
297
+ noEmpty: opts.noEmpty,
298
+ noExtra: opts.noExtra,
299
+ strict: opts.strict,
300
+ detectSecrets: opts.detectSecrets,
301
+ checkConsistency: true
302
+ });
303
+
304
+ if (opts.json) {
305
+ console.log(JSON.stringify(result, null, 2));
306
+ return result.valid ? 0 : 1;
307
+ }
308
+
309
+ // Use formatted output
310
+ const output = formatMonorepoResult(result, {
311
+ colors: !opts.quiet,
312
+ verbose: opts.verbose
313
+ });
314
+ console.log(output);
315
+
316
+ return result.valid ? 0 : 1;
317
+ }
318
+
280
319
  function main() {
281
320
  const args = process.argv.slice(2);
282
321
  const opts = parseArgs(args);
@@ -287,6 +326,9 @@ function main() {
287
326
  case 'check':
288
327
  exitCode = runCheck(opts);
289
328
  break;
329
+ case 'monorepo':
330
+ exitCode = runMonorepo(opts);
331
+ break;
290
332
  case 'compare':
291
333
  exitCode = runCompare(opts);
292
334
  break;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@claude-agent/envcheck",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Validate .env files, compare with .env.example, find missing or empty variables",
5
5
  "main": "src/index.js",
6
+ "types": "types/index.d.ts",
6
7
  "bin": {
7
8
  "envcheck": "bin/cli.js"
8
9
  },
@@ -22,7 +23,10 @@
22
23
  "pre-commit",
23
24
  "git-hooks",
24
25
  "secrets",
25
- "security"
26
+ "security",
27
+ "monorepo",
28
+ "turborepo",
29
+ "workspaces"
26
30
  ],
27
31
  "author": "Claude Agent <claude-agent@agentmail.to>",
28
32
  "license": "MIT",
@@ -40,6 +44,10 @@
40
44
  "files": [
41
45
  "src/",
42
46
  "bin/",
47
+ "types/",
43
48
  "pre-commit-hook.sh"
44
- ]
49
+ ],
50
+ "devDependencies": {
51
+ "typescript": "^5.9.3"
52
+ }
45
53
  }
package/src/index.js CHANGED
@@ -683,6 +683,278 @@ function get(filePath, key) {
683
683
  return env.variables[key];
684
684
  }
685
685
 
686
+ /**
687
+ * Find directories that might contain apps/packages in a monorepo
688
+ * @param {string} rootDir - Root directory to scan
689
+ * @returns {string[]} Array of directory paths
690
+ */
691
+ function findMonorepoApps(rootDir) {
692
+ const apps = [];
693
+ const basePath = path.resolve(rootDir);
694
+
695
+ // Common monorepo patterns
696
+ const patterns = ['apps', 'packages', 'workspaces', 'services', 'libs'];
697
+
698
+ for (const pattern of patterns) {
699
+ const dir = path.join(basePath, pattern);
700
+ if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
701
+ // Get all subdirectories
702
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
703
+ for (const entry of entries) {
704
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
705
+ apps.push(path.join(dir, entry.name));
706
+ }
707
+ }
708
+ }
709
+ }
710
+
711
+ // Also check root for .env.example (some monorepos have root-level env)
712
+ if (fs.existsSync(path.join(basePath, '.env.example')) ||
713
+ fs.existsSync(path.join(basePath, '.env'))) {
714
+ apps.unshift(basePath);
715
+ }
716
+
717
+ return apps;
718
+ }
719
+
720
+ /**
721
+ * Scan a monorepo for env file issues
722
+ * @param {string} rootDir - Root directory of monorepo
723
+ * @param {Object} options - Scan options
724
+ * @returns {Object} Monorepo scan result
725
+ */
726
+ function scanMonorepo(rootDir, options = {}) {
727
+ const {
728
+ noEmpty = false,
729
+ noExtra = false,
730
+ strict = false,
731
+ checkConsistency = true,
732
+ detectSecrets = false
733
+ } = options;
734
+
735
+ const basePath = path.resolve(rootDir);
736
+ const apps = findMonorepoApps(basePath);
737
+
738
+ const result = {
739
+ root: basePath,
740
+ valid: true,
741
+ apps: [],
742
+ summary: {
743
+ total: apps.length,
744
+ passed: 0,
745
+ failed: 0,
746
+ skipped: 0,
747
+ errors: 0,
748
+ warnings: 0
749
+ },
750
+ consistency: {
751
+ sharedVars: {}, // Variables that appear in multiple apps
752
+ mismatches: [] // Variables with different types/values across apps
753
+ }
754
+ };
755
+
756
+ // Track all variables across apps for consistency checking
757
+ const allVars = {}; // { varName: [{ app, value, type }] }
758
+
759
+ for (const appDir of apps) {
760
+ const appName = path.relative(basePath, appDir) || '.';
761
+ const envPath = path.join(appDir, '.env');
762
+ const examplePath = path.join(appDir, '.env.example');
763
+
764
+ const appResult = {
765
+ name: appName,
766
+ path: appDir,
767
+ hasEnv: fs.existsSync(envPath),
768
+ hasExample: fs.existsSync(examplePath),
769
+ valid: true,
770
+ issues: [],
771
+ variables: []
772
+ };
773
+
774
+ // Skip if no example file (can't validate)
775
+ if (!appResult.hasExample) {
776
+ appResult.skipped = true;
777
+ appResult.reason = 'No .env.example found';
778
+ result.apps.push(appResult);
779
+ result.summary.skipped++;
780
+ continue;
781
+ }
782
+
783
+ // If no .env but has example, that's also a problem
784
+ if (!appResult.hasEnv) {
785
+ appResult.valid = false;
786
+ appResult.issues.push({
787
+ type: 'warning',
788
+ message: 'No .env file found (but .env.example exists)'
789
+ });
790
+ }
791
+
792
+ // Run check if both files exist
793
+ if (appResult.hasEnv) {
794
+ const checkResult = check(envPath, {
795
+ examplePath,
796
+ noEmpty,
797
+ noExtra,
798
+ strict,
799
+ validateTypes: true,
800
+ detectSecrets
801
+ });
802
+
803
+ appResult.valid = checkResult.valid;
804
+ appResult.issues = checkResult.issues;
805
+ appResult.variables = Object.keys(checkResult.env?.variables || {});
806
+
807
+ // Track variables for consistency check
808
+ if (checkConsistency && checkResult.env?.variables) {
809
+ const example = readEnvFile(examplePath);
810
+ for (const [varName, value] of Object.entries(checkResult.env.variables)) {
811
+ if (!allVars[varName]) {
812
+ allVars[varName] = [];
813
+ }
814
+ allVars[varName].push({
815
+ app: appName,
816
+ value,
817
+ type: example.typeHints?.[varName] || null
818
+ });
819
+ }
820
+ }
821
+ }
822
+
823
+ // Update summary
824
+ if (appResult.skipped) {
825
+ // Already counted above
826
+ } else if (appResult.valid) {
827
+ result.summary.passed++;
828
+ } else {
829
+ result.summary.failed++;
830
+ result.valid = false;
831
+ }
832
+
833
+ const errors = appResult.issues.filter(i => i.type === 'error').length;
834
+ const warnings = appResult.issues.filter(i => i.type === 'warning').length;
835
+ result.summary.errors += errors;
836
+ result.summary.warnings += warnings;
837
+
838
+ result.apps.push(appResult);
839
+ }
840
+
841
+ // Check consistency across apps
842
+ if (checkConsistency) {
843
+ for (const [varName, occurrences] of Object.entries(allVars)) {
844
+ if (occurrences.length > 1) {
845
+ result.consistency.sharedVars[varName] = occurrences.map(o => o.app);
846
+
847
+ // Check for type mismatches
848
+ const types = new Set(occurrences.map(o => o.type).filter(Boolean));
849
+ if (types.size > 1) {
850
+ result.consistency.mismatches.push({
851
+ variable: varName,
852
+ issue: 'type_mismatch',
853
+ details: occurrences.map(o => ({ app: o.app, type: o.type }))
854
+ });
855
+ result.valid = false;
856
+ }
857
+ }
858
+ }
859
+ }
860
+
861
+ return result;
862
+ }
863
+
864
+ /**
865
+ * Format monorepo result for CLI output
866
+ * @param {Object} result - Monorepo scan result
867
+ * @param {Object} options - Format options
868
+ * @returns {string} Formatted output
869
+ */
870
+ function formatMonorepoResult(result, options = {}) {
871
+ const { colors = true, verbose = false } = options;
872
+
873
+ const c = colors ? {
874
+ green: '\x1b[32m',
875
+ red: '\x1b[31m',
876
+ yellow: '\x1b[33m',
877
+ dim: '\x1b[2m',
878
+ reset: '\x1b[0m',
879
+ bold: '\x1b[1m'
880
+ } : { green: '', red: '', yellow: '', dim: '', reset: '', bold: '' };
881
+
882
+ const lines = [];
883
+
884
+ lines.push(`${c.bold}Monorepo Environment Check${c.reset}`);
885
+ lines.push(`Root: ${result.root}`);
886
+ lines.push('');
887
+
888
+ for (const app of result.apps) {
889
+ const icon = app.skipped ? `${c.dim}○${c.reset}` :
890
+ app.valid ? `${c.green}✓${c.reset}` :
891
+ `${c.red}✗${c.reset}`;
892
+
893
+ let status = '';
894
+ if (app.skipped) {
895
+ status = `${c.dim}skipped (${app.reason})${c.reset}`;
896
+ } else {
897
+ const errors = app.issues.filter(i => i.type === 'error').length;
898
+ const warnings = app.issues.filter(i => i.type === 'warning').length;
899
+
900
+ if (errors === 0 && warnings === 0) {
901
+ status = `${c.green}passed${c.reset}`;
902
+ } else if (errors > 0) {
903
+ status = `${c.red}${errors} error(s)${c.reset}`;
904
+ if (warnings > 0) status += `, ${c.yellow}${warnings} warning(s)${c.reset}`;
905
+ } else {
906
+ status = `${c.yellow}${warnings} warning(s)${c.reset}`;
907
+ }
908
+ }
909
+
910
+ lines.push(`${icon} ${app.name}: ${status}`);
911
+
912
+ // Show details in verbose mode
913
+ if (verbose && !app.skipped && app.issues.length > 0) {
914
+ for (const issue of app.issues) {
915
+ const prefix = issue.type === 'error' ? `${c.red} ✗${c.reset}` : `${c.yellow} !${c.reset}`;
916
+ lines.push(`${prefix} ${issue.message}`);
917
+ }
918
+ }
919
+ }
920
+
921
+ lines.push('');
922
+
923
+ // Summary
924
+ lines.push(`${c.bold}Summary:${c.reset} ${result.summary.total} apps scanned`);
925
+ if (result.summary.passed > 0) {
926
+ lines.push(` ${c.green}✓${c.reset} ${result.summary.passed} passed`);
927
+ }
928
+ if (result.summary.failed > 0) {
929
+ lines.push(` ${c.red}✗${c.reset} ${result.summary.failed} failed`);
930
+ }
931
+ if (result.summary.skipped > 0) {
932
+ lines.push(` ${c.dim}○${c.reset} ${result.summary.skipped} skipped`);
933
+ }
934
+
935
+ // Consistency issues
936
+ if (result.consistency.mismatches.length > 0) {
937
+ lines.push('');
938
+ lines.push(`${c.yellow}Consistency Issues:${c.reset}`);
939
+ for (const mismatch of result.consistency.mismatches) {
940
+ lines.push(` ${c.yellow}!${c.reset} ${mismatch.variable}: ${mismatch.issue}`);
941
+ for (const detail of mismatch.details) {
942
+ lines.push(` - ${detail.app}: type=${detail.type || 'unspecified'}`);
943
+ }
944
+ }
945
+ }
946
+
947
+ // Final status
948
+ lines.push('');
949
+ if (result.valid) {
950
+ lines.push(`${c.green}✓ All checks passed${c.reset}`);
951
+ } else {
952
+ lines.push(`${c.red}✗ ${result.summary.errors} error(s), ${result.summary.warnings} warning(s)${c.reset}`);
953
+ }
954
+
955
+ return lines.join('\n');
956
+ }
957
+
686
958
  module.exports = {
687
959
  parse: parseEnv,
688
960
  parseValue,
@@ -697,5 +969,9 @@ module.exports = {
697
969
  check,
698
970
  generate,
699
971
  list,
700
- get
972
+ get,
973
+ // Monorepo support
974
+ findMonorepoApps,
975
+ scanMonorepo,
976
+ formatMonorepoResult
701
977
  };
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Type definitions for @claude-agent/envcheck
3
+ * Static environment variable validation for Node.js
4
+ */
5
+
6
+ // ============================================================================
7
+ // Basic Types
8
+ // ============================================================================
9
+
10
+ /**
11
+ * Validation type names supported by envcheck
12
+ */
13
+ export type ValidationType =
14
+ | 'url'
15
+ | 'port'
16
+ | 'boolean'
17
+ | 'bool'
18
+ | 'email'
19
+ | 'number'
20
+ | 'integer'
21
+ | 'int'
22
+ | 'string'
23
+ | 'str'
24
+ | 'json'
25
+ | 'uuid';
26
+
27
+ /**
28
+ * Issue severity level
29
+ */
30
+ export type IssueType = 'error' | 'warning';
31
+
32
+ /**
33
+ * Issue found during validation
34
+ */
35
+ export interface Issue {
36
+ type: IssueType;
37
+ message: string;
38
+ line?: number;
39
+ }
40
+
41
+ /**
42
+ * Result of type validation
43
+ */
44
+ export interface TypeValidationResult {
45
+ valid: boolean;
46
+ message?: string;
47
+ }
48
+
49
+ /**
50
+ * Type validator function
51
+ */
52
+ export type TypeValidator = (value: string) => TypeValidationResult;
53
+
54
+ // ============================================================================
55
+ // Parse Types
56
+ // ============================================================================
57
+
58
+ /**
59
+ * Result of parsing a .env file
60
+ */
61
+ export interface ParseResult {
62
+ variables: Record<string, string>;
63
+ errors: Array<{
64
+ line: number;
65
+ message: string;
66
+ content: string;
67
+ }>;
68
+ warnings: Array<{
69
+ line: number;
70
+ message: string;
71
+ content: string;
72
+ }>;
73
+ lineInfo: Record<string, number>;
74
+ typeHints: Record<string, string>;
75
+ }
76
+
77
+ /**
78
+ * Result of reading an env file
79
+ */
80
+ export interface EnvFileResult extends ParseResult {
81
+ exists: boolean;
82
+ path: string;
83
+ }
84
+
85
+ // ============================================================================
86
+ // Compare Types
87
+ // ============================================================================
88
+
89
+ /**
90
+ * Item missing from env file
91
+ */
92
+ export interface MissingItem {
93
+ key: string;
94
+ exampleValue: string;
95
+ line: number;
96
+ }
97
+
98
+ /**
99
+ * Extra item in env file
100
+ */
101
+ export interface ExtraItem {
102
+ key: string;
103
+ value: string;
104
+ line: number;
105
+ }
106
+
107
+ /**
108
+ * Empty item in env file
109
+ */
110
+ export interface EmptyItem {
111
+ key: string;
112
+ line: number;
113
+ }
114
+
115
+ /**
116
+ * Result of comparing two env files
117
+ */
118
+ export interface CompareResult {
119
+ env: EnvFileResult;
120
+ example: EnvFileResult;
121
+ missing: MissingItem[];
122
+ extra: ExtraItem[];
123
+ empty: EmptyItem[];
124
+ different: Array<{
125
+ key: string;
126
+ envValue: string;
127
+ exampleValue: string;
128
+ }>;
129
+ }
130
+
131
+ // ============================================================================
132
+ // Validate Types
133
+ // ============================================================================
134
+
135
+ /**
136
+ * Options for validate function
137
+ */
138
+ export interface ValidateOptions {
139
+ /** List of required variable names */
140
+ required?: string[];
141
+ /** Warn on empty values */
142
+ noEmpty?: boolean;
143
+ /** Type specifications for variables */
144
+ types?: Record<string, ValidationType>;
145
+ /** Enable type validation from hints */
146
+ validateTypes?: boolean;
147
+ /** Enable secret detection */
148
+ detectSecrets?: boolean;
149
+ }
150
+
151
+ /**
152
+ * Result of validation
153
+ */
154
+ export interface ValidateResult {
155
+ valid: boolean;
156
+ env: EnvFileResult;
157
+ issues: Issue[];
158
+ }
159
+
160
+ // ============================================================================
161
+ // Check Types
162
+ // ============================================================================
163
+
164
+ /**
165
+ * Options for check function
166
+ */
167
+ export interface CheckOptions {
168
+ /** Path to example file */
169
+ examplePath?: string;
170
+ /** List of required variable names */
171
+ required?: string[];
172
+ /** Warn on empty values */
173
+ noEmpty?: boolean;
174
+ /** Error on extra variables */
175
+ noExtra?: boolean;
176
+ /** Treat warnings as errors */
177
+ strict?: boolean;
178
+ /** Type specifications for variables */
179
+ types?: Record<string, ValidationType>;
180
+ /** Enable type validation */
181
+ validateTypes?: boolean;
182
+ /** Enable secret detection */
183
+ detectSecrets?: boolean;
184
+ }
185
+
186
+ /**
187
+ * Result of check function
188
+ */
189
+ export interface CheckResult {
190
+ valid: boolean;
191
+ issues: Issue[];
192
+ summary: {
193
+ errors: number;
194
+ warnings: number;
195
+ };
196
+ env?: EnvFileResult;
197
+ comparison?: CompareResult;
198
+ }
199
+
200
+ // ============================================================================
201
+ // Secret Detection Types
202
+ // ============================================================================
203
+
204
+ /**
205
+ * Secret pattern definition
206
+ */
207
+ export interface SecretPattern {
208
+ regex: RegExp;
209
+ description: string;
210
+ keyPattern?: RegExp;
211
+ }
212
+
213
+ /**
214
+ * Result of secret detection
215
+ */
216
+ export interface SecretDetectionResult {
217
+ detected: boolean;
218
+ description: string;
219
+ message: string;
220
+ }
221
+
222
+ // ============================================================================
223
+ // Monorepo Types
224
+ // ============================================================================
225
+
226
+ /**
227
+ * Options for monorepo scanning
228
+ */
229
+ export interface ScanMonorepoOptions {
230
+ /** Warn on empty values */
231
+ noEmpty?: boolean;
232
+ /** Error on extra variables */
233
+ noExtra?: boolean;
234
+ /** Treat warnings as errors */
235
+ strict?: boolean;
236
+ /** Check consistency across apps */
237
+ checkConsistency?: boolean;
238
+ /** Enable secret detection */
239
+ detectSecrets?: boolean;
240
+ }
241
+
242
+ /**
243
+ * Result for a single app in monorepo scan
244
+ */
245
+ export interface MonorepoAppResult {
246
+ name: string;
247
+ path: string;
248
+ hasEnv: boolean;
249
+ hasExample: boolean;
250
+ valid: boolean;
251
+ issues: Issue[];
252
+ variables: string[];
253
+ skipped?: boolean;
254
+ reason?: string;
255
+ }
256
+
257
+ /**
258
+ * Consistency mismatch in monorepo
259
+ */
260
+ export interface ConsistencyMismatch {
261
+ variable: string;
262
+ issue: string;
263
+ details: Array<{
264
+ app: string;
265
+ type: string | null;
266
+ value?: string;
267
+ }>;
268
+ }
269
+
270
+ /**
271
+ * Result of scanning a monorepo
272
+ */
273
+ export interface MonorepoScanResult {
274
+ root: string;
275
+ valid: boolean;
276
+ apps: MonorepoAppResult[];
277
+ summary: {
278
+ total: number;
279
+ passed: number;
280
+ failed: number;
281
+ skipped: number;
282
+ errors: number;
283
+ warnings: number;
284
+ };
285
+ consistency: {
286
+ sharedVars: Record<string, string[]>;
287
+ mismatches: ConsistencyMismatch[];
288
+ };
289
+ }
290
+
291
+ /**
292
+ * Options for formatting monorepo result
293
+ */
294
+ export interface FormatMonorepoOptions {
295
+ /** Enable ANSI colors */
296
+ colors?: boolean;
297
+ /** Show detailed issues */
298
+ verbose?: boolean;
299
+ }
300
+
301
+ // ============================================================================
302
+ // Exported Functions
303
+ // ============================================================================
304
+
305
+ /**
306
+ * Parse .env file content into an object
307
+ * @param content - Raw .env file content
308
+ * @returns Parsed result with variables and metadata
309
+ */
310
+ export function parse(content: string): ParseResult;
311
+
312
+ /**
313
+ * Parse a single value, handling quotes and escapes
314
+ * @param raw - Raw value string
315
+ * @returns Parsed value
316
+ */
317
+ export function parseValue(raw: string): string;
318
+
319
+ /**
320
+ * Read and parse a .env file
321
+ * @param filePath - Path to .env file
322
+ * @returns Parsed file result
323
+ */
324
+ export function readEnvFile(filePath: string): EnvFileResult;
325
+
326
+ /**
327
+ * Compare two env files
328
+ * @param envPath - Path to .env file
329
+ * @param examplePath - Path to .env.example file
330
+ * @returns Comparison result
331
+ */
332
+ export function compare(envPath: string, examplePath: string): CompareResult;
333
+
334
+ /**
335
+ * Validate an env file
336
+ * @param filePath - Path to .env file
337
+ * @param options - Validation options
338
+ * @returns Validation result
339
+ */
340
+ export function validate(filePath: string, options?: ValidateOptions): ValidateResult;
341
+
342
+ /**
343
+ * Validate a value against a type
344
+ * @param value - Value to validate
345
+ * @param type - Type name
346
+ * @returns Validation result
347
+ */
348
+ export function validateType(value: string, type: ValidationType): TypeValidationResult;
349
+
350
+ /**
351
+ * Check an env file against example and validate
352
+ * @param envPath - Path to .env file
353
+ * @param options - Check options
354
+ * @returns Check result
355
+ */
356
+ export function check(envPath: string, options?: CheckOptions): CheckResult;
357
+
358
+ /**
359
+ * Generate a .env file from .env.example
360
+ * @param examplePath - Path to .env.example
361
+ * @param defaults - Default values to fill in
362
+ * @returns Generated .env content
363
+ */
364
+ export function generate(examplePath: string, defaults?: Record<string, string>): string;
365
+
366
+ /**
367
+ * Get list of variable names from file
368
+ * @param filePath - Path to env file
369
+ * @returns Array of variable names
370
+ */
371
+ export function list(filePath: string): string[];
372
+
373
+ /**
374
+ * Get a specific variable value
375
+ * @param filePath - Path to env file
376
+ * @param key - Variable name
377
+ * @returns Value or undefined
378
+ */
379
+ export function get(filePath: string, key: string): string | undefined;
380
+
381
+ /**
382
+ * Detect potential secrets in environment variables
383
+ * @param key - Variable name
384
+ * @param value - Variable value
385
+ * @returns Detection result or null if no secret detected
386
+ */
387
+ export function detectSecret(key: string, value: string): SecretDetectionResult | null;
388
+
389
+ /**
390
+ * Check if a value looks like a placeholder
391
+ * @param value - Value to check
392
+ * @returns True if it looks like a placeholder
393
+ */
394
+ export function isPlaceholder(value: string): boolean;
395
+
396
+ /**
397
+ * Find directories that might contain apps/packages in a monorepo
398
+ * @param rootDir - Root directory to scan
399
+ * @returns Array of directory paths
400
+ */
401
+ export function findMonorepoApps(rootDir: string): string[];
402
+
403
+ /**
404
+ * Scan a monorepo for env file issues
405
+ * @param rootDir - Root directory of monorepo
406
+ * @param options - Scan options
407
+ * @returns Monorepo scan result
408
+ */
409
+ export function scanMonorepo(rootDir: string, options?: ScanMonorepoOptions): MonorepoScanResult;
410
+
411
+ /**
412
+ * Format monorepo result for CLI output
413
+ * @param result - Monorepo scan result
414
+ * @param options - Format options
415
+ * @returns Formatted string
416
+ */
417
+ export function formatMonorepoResult(result: MonorepoScanResult, options?: FormatMonorepoOptions): string;
418
+
419
+ // ============================================================================
420
+ // Exported Constants
421
+ // ============================================================================
422
+
423
+ /**
424
+ * Map of type names to validator functions
425
+ */
426
+ export const typeValidators: Record<ValidationType, TypeValidator>;
427
+
428
+ /**
429
+ * Array of secret detection patterns
430
+ */
431
+ export const secretPatterns: SecretPattern[];