@danielszlaski/envguard 0.1.2 → 0.1.4

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.
Files changed (38) hide show
  1. package/.envguardrc.example.json +1 -0
  2. package/.envguardrc.json +16 -0
  3. package/.github/FUNDING.yml +15 -0
  4. package/LICENSE +1 -1
  5. package/README.md +246 -11
  6. package/dist/analyzer/envAnalyzer.d.ts +8 -2
  7. package/dist/analyzer/envAnalyzer.d.ts.map +1 -1
  8. package/dist/analyzer/envAnalyzer.js +22 -8
  9. package/dist/analyzer/envAnalyzer.js.map +1 -1
  10. package/dist/cli.js +25 -3
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/fix.d.ts.map +1 -1
  13. package/dist/commands/fix.js +32 -7
  14. package/dist/commands/fix.js.map +1 -1
  15. package/dist/commands/scan.d.ts +1 -0
  16. package/dist/commands/scan.d.ts.map +1 -1
  17. package/dist/commands/scan.js +150 -41
  18. package/dist/commands/scan.js.map +1 -1
  19. package/dist/config/configLoader.d.ts +6 -0
  20. package/dist/config/configLoader.d.ts.map +1 -1
  21. package/dist/config/configLoader.js +1 -0
  22. package/dist/config/configLoader.js.map +1 -1
  23. package/dist/scanner/codeScanner.d.ts +5 -2
  24. package/dist/scanner/codeScanner.d.ts.map +1 -1
  25. package/dist/scanner/codeScanner.js +72 -25
  26. package/dist/scanner/codeScanner.js.map +1 -1
  27. package/dist/types.d.ts +6 -1
  28. package/dist/types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/src/analyzer/envAnalyzer.ts +27 -10
  31. package/src/cli.ts +29 -3
  32. package/src/commands/fix.ts +40 -9
  33. package/src/commands/scan.ts +168 -47
  34. package/src/config/configLoader.ts +8 -0
  35. package/src/scanner/codeScanner.ts +97 -28
  36. package/src/types.ts +3 -1
  37. package/test-project/src/lambda2/handler2.js +1 -1
  38. package/test-project/.envguardrc.json +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"codeScanner.js","sourceRoot":"","sources":["../../src/scanner/codeScanner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codeScanner.js","sourceRoot":"","sources":["../../src/scanner/codeScanner.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export interface EnvUsage {
2
2
  varName: string;
3
3
  locations: string[];
4
+ hasFallback?: boolean;
4
5
  }
5
6
  export interface EnvDefinition {
6
7
  varName: string;
@@ -11,13 +12,17 @@ export interface EnvDefinition {
11
12
  }
12
13
  export interface Issue {
13
14
  type: 'missing' | 'unused' | 'undocumented';
15
+ severity: 'error' | 'warning' | 'info';
14
16
  varName: string;
15
17
  details: string;
16
18
  locations?: string[];
17
19
  }
18
20
  export interface ScanResult {
19
21
  issues: Issue[];
20
- usedVars: Map<string, string[]>;
22
+ usedVars: Map<string, {
23
+ locations: string[];
24
+ hasFallback: boolean;
25
+ }>;
21
26
  definedVars: Set<string>;
22
27
  exampleVars: Set<string>;
23
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;IAC5C,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACrE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC1B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielszlaski/envguard",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tool to keep environment variables in sync with your codebase",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -3,20 +3,28 @@ import { EnvEntry } from '../parser/envParser';
3
3
 
4
4
  export class EnvAnalyzer {
5
5
  analyze(
6
- usedVars: Map<string, string[]>,
6
+ usedVars: Map<string, { locations: string[], hasFallback: boolean }>,
7
7
  definedVars: Map<string, EnvEntry>,
8
- exampleVars: Set<string>
8
+ exampleVars: Set<string>,
9
+ detectFallbacks: boolean = true
9
10
  ): ScanResult {
10
11
  const issues: Issue[] = [];
11
12
 
12
13
  // Issue 1: Variables used in code but missing from .env
13
- for (const [varName, locations] of usedVars.entries()) {
14
+ for (const [varName, usage] of usedVars.entries()) {
14
15
  if (!definedVars.has(varName)) {
16
+ // If detectFallbacks is enabled and variable has a fallback, it's a WARNING, otherwise ERROR
17
+ const severity = (detectFallbacks && usage.hasFallback) ? 'warning' : 'error';
18
+ const details = (detectFallbacks && usage.hasFallback)
19
+ ? `Used in code with fallback/default but not defined in .env`
20
+ : `Used in code but not defined in .env`;
21
+
15
22
  issues.push({
16
23
  type: 'missing',
24
+ severity,
17
25
  varName,
18
- details: `Used in code but not defined in .env`,
19
- locations,
26
+ details,
27
+ locations: usage.locations,
20
28
  });
21
29
  }
22
30
  }
@@ -26,6 +34,7 @@ export class EnvAnalyzer {
26
34
  if (!usedVars.has(varName)) {
27
35
  issues.push({
28
36
  type: 'unused',
37
+ severity: 'info',
29
38
  varName,
30
39
  details: `Defined in .env but never used in code`,
31
40
  });
@@ -33,13 +42,20 @@ export class EnvAnalyzer {
33
42
  }
34
43
 
35
44
  // Issue 3: Variables used in code but not in .env.example
36
- for (const [varName, locations] of usedVars.entries()) {
45
+ for (const [varName, usage] of usedVars.entries()) {
37
46
  if (!exampleVars.has(varName)) {
47
+ // If detectFallbacks is enabled and variable has a fallback, it's less critical for documentation
48
+ const severity = (detectFallbacks && usage.hasFallback) ? 'info' : 'warning';
49
+ const details = (detectFallbacks && usage.hasFallback)
50
+ ? `Used in code with fallback but missing from .env.example`
51
+ : `Used in code but missing from .env.example`;
52
+
38
53
  issues.push({
39
54
  type: 'undocumented',
55
+ severity,
40
56
  varName,
41
- details: `Used in code but missing from .env.example`,
42
- locations,
57
+ details,
58
+ locations: usage.locations,
43
59
  });
44
60
  }
45
61
  }
@@ -53,7 +69,7 @@ export class EnvAnalyzer {
53
69
  }
54
70
 
55
71
  generateExampleContent(
56
- usedVars: Map<string, string[]>,
72
+ usedVars: Map<string, { locations: string[], hasFallback: boolean }>,
57
73
  existingEntries: Map<string, EnvEntry>
58
74
  ): string {
59
75
  let content = '# Auto-generated by envguard\n';
@@ -62,7 +78,8 @@ export class EnvAnalyzer {
62
78
  const sortedVars = Array.from(usedVars.keys()).sort();
63
79
 
64
80
  for (const varName of sortedVars) {
65
- const locations = usedVars.get(varName)!;
81
+ const usage = usedVars.get(varName)!;
82
+ const locations = usage.locations;
66
83
  const existingEntry = existingEntries.get(varName);
67
84
 
68
85
  // Add location comments
package/src/cli.ts CHANGED
@@ -18,8 +18,22 @@ program
18
18
  .description('Scan codebase and compare with .env files')
19
19
  .option('--ci', 'Exit with error code if issues found (for CI/CD)')
20
20
  .option('--strict', 'Report all variables including known runtime variables (AWS_REGION, NODE_ENV, etc.)')
21
- .action(async (options) => {
21
+ .option('--no-detect-fallbacks', 'Treat all missing variables as errors, ignoring fallback detection')
22
+ .action(async (cmd, command) => {
22
23
  try {
24
+ // Build options object, only including detectFallbacks if the flag was used
25
+ const options: any = {
26
+ ci: cmd.ci,
27
+ strict: cmd.strict
28
+ };
29
+
30
+ // Check if the --no-detect-fallbacks flag was explicitly provided
31
+ // Commander adds it to the command's options when the flag is used
32
+ const flagProvided = command.parent?.rawArgs.some((arg: string) => arg.includes('detect-fallback'));
33
+ if (flagProvided) {
34
+ options.detectFallbacks = cmd.detectFallbacks;
35
+ }
36
+
23
37
  await scanCommand(options);
24
38
  } catch (error) {
25
39
  Logger.error(`${error}`);
@@ -43,9 +57,21 @@ program
43
57
  .command('check')
44
58
  .description('Check for issues (alias for scan --ci)')
45
59
  .option('--strict', 'Report all variables including known runtime variables (AWS_REGION, NODE_ENV, etc.)')
46
- .action(async (options) => {
60
+ .option('--no-detect-fallbacks', 'Treat all missing variables as errors, ignoring fallback detection')
61
+ .action(async (cmd, command) => {
47
62
  try {
48
- await scanCommand({ ci: true, strict: options.strict });
63
+ const options: any = {
64
+ ci: true,
65
+ strict: cmd.strict
66
+ };
67
+
68
+ // Check if the --no-detect-fallbacks flag was explicitly provided
69
+ const flagProvided = command.parent?.rawArgs.some((arg: string) => arg.includes('detect-fallback'));
70
+ if (flagProvided) {
71
+ options.detectFallbacks = cmd.detectFallbacks;
72
+ }
73
+
74
+ await scanCommand(options);
49
75
  } catch (error) {
50
76
  Logger.error(`${error}`);
51
77
  process.exit(1);
@@ -10,8 +10,19 @@ export async function fixCommand() {
10
10
 
11
11
  Logger.startSpinner('Generating .env.example files...');
12
12
 
13
+ // Load configuration
14
+ const { ConfigLoader } = require('../config/configLoader');
15
+ const config = ConfigLoader.loadConfig(rootDir);
16
+
13
17
  // Step 1: Find all .env files
14
- const scanner = new CodeScanner(rootDir);
18
+ const excludePatterns = [
19
+ 'node_modules',
20
+ 'dist',
21
+ 'build',
22
+ '.git',
23
+ ...(config.exclude || []),
24
+ ];
25
+ const scanner = new CodeScanner(rootDir, excludePatterns);
15
26
  const envFiles = await scanner.findEnvFiles();
16
27
 
17
28
  Logger.stopSpinner();
@@ -38,7 +49,21 @@ export async function fixCommand() {
38
49
  Logger.path(`Processing ${displayPath}/`);
39
50
 
40
51
  // Step 3: Scan code files in this directory and subdirectories
41
- const usedVars = await scanDirectoryForVars(rootDir, envDir, scanner);
52
+ const allUsedVars = await scanDirectoryForVars(rootDir, envDir, scanner, config.exclude);
53
+
54
+ // Filter out ignored variables based on config (they shouldn't be in .env.example)
55
+ const { isKnownRuntimeVar } = require('../constants/knownEnvVars');
56
+ const usedVars = new Map<string, { locations: string[], hasFallback: boolean }>();
57
+
58
+ for (const [varName, usage] of allUsedVars.entries()) {
59
+ const isCustomIgnored = ConfigLoader.shouldIgnoreVar(varName, config);
60
+ const isRuntimeVar = isKnownRuntimeVar(varName);
61
+
62
+ // Skip known runtime variables and custom ignore vars
63
+ if (!isRuntimeVar && !isCustomIgnored) {
64
+ usedVars.set(varName, usage);
65
+ }
66
+ }
42
67
 
43
68
  if (usedVars.size === 0) {
44
69
  Logger.info('No environment variables found in code', true);
@@ -82,29 +107,35 @@ export async function fixCommand() {
82
107
  async function scanDirectoryForVars(
83
108
  rootDir: string,
84
109
  targetDir: string,
85
- scanner: CodeScanner
86
- ): Promise<Map<string, string[]>> {
87
- const envVars = new Map<string, string[]>();
110
+ scanner: CodeScanner,
111
+ excludePatterns: string[] = []
112
+ ): Promise<Map<string, { locations: string[], hasFallback: boolean }>> {
113
+ const envVars = new Map<string, { locations: string[], hasFallback: boolean }>();
88
114
  const { glob } = require('glob');
89
115
 
90
116
  // Find all code files in this directory and subdirectories
91
117
  const relativeDir = path.relative(rootDir, targetDir);
92
118
  const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
93
119
 
120
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
121
+ const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
122
+
94
123
  const files = await glob(pattern, {
95
124
  cwd: rootDir,
96
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
125
+ ignore: [...defaultIgnore, ...customIgnore],
97
126
  absolute: true,
98
127
  });
99
128
 
100
129
  for (const file of files) {
101
130
  const vars = await (scanner as any).scanFile(file);
102
- for (const varName of vars) {
131
+ for (const [varName, hasFallback] of vars.entries()) {
103
132
  const relativePath = path.relative(rootDir, file);
104
133
  if (!envVars.has(varName)) {
105
- envVars.set(varName, []);
134
+ envVars.set(varName, { locations: [], hasFallback: false });
106
135
  }
107
- envVars.get(varName)!.push(relativePath);
136
+ const entry = envVars.get(varName)!;
137
+ entry.locations.push(relativePath);
138
+ entry.hasFallback = entry.hasFallback || hasFallback;
108
139
  }
109
140
  }
110
141
 
@@ -10,7 +10,7 @@ import { isKnownRuntimeVar, getRuntimeVarCategory } from '../constants/knownEnvV
10
10
  import { ConfigLoader } from '../config/configLoader';
11
11
  import { Logger } from '../utils/logger';
12
12
 
13
- export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
13
+ export async function scanCommand(options: { ci?: boolean; strict?: boolean; detectFallbacks?: boolean }) {
14
14
  const rootDir = process.cwd();
15
15
 
16
16
  // Load configuration
@@ -18,11 +18,19 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
18
18
 
19
19
  // CLI options override config file
20
20
  const strictMode = options.strict !== undefined ? options.strict : config.strict;
21
+ const detectFallbacks = options.detectFallbacks !== undefined ? options.detectFallbacks : (config.detectFallbacks !== undefined ? config.detectFallbacks : true);
21
22
 
22
23
  Logger.startSpinner('Scanning codebase for environment variables...');
23
24
 
24
25
  // Step 1: Find all .env files and serverless.yml files
25
- const scanner = new CodeScanner(rootDir);
26
+ const excludePatterns = [
27
+ 'node_modules',
28
+ 'dist',
29
+ 'build',
30
+ '.git',
31
+ ...(config.exclude || []),
32
+ ];
33
+ const scanner = new CodeScanner(rootDir, excludePatterns);
26
34
  const envFiles = await scanner.findEnvFiles();
27
35
  const serverlessFiles = await scanner.findServerlessFiles();
28
36
 
@@ -55,7 +63,7 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
55
63
  Logger.info(`Found ${serverlessVars.size} variable(s) in serverless.yml`, true);
56
64
 
57
65
  // Scan code files in this directory to see what's actually used
58
- const usedVars = await scanDirectoryForCodeVars(rootDir, serverlessDir, scanner);
66
+ const usedVars = await scanDirectoryForCodeVars(rootDir, serverlessDir, scanner, config.exclude);
59
67
  Logger.info(`Found ${usedVars.size} variable(s) used in code`, true);
60
68
  Logger.blank();
61
69
 
@@ -72,10 +80,10 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
72
80
  }
73
81
 
74
82
  // Check for variables used in code but not defined in serverless.yml
75
- const missingFromServerless: Array<{ varName: string; locations: string[]; category?: string }> = [];
83
+ const missingFromServerless: Array<{ varName: string; locations: string[]; hasFallback: boolean; category?: string }> = [];
76
84
  const skippedRuntimeVars: Array<{ varName: string; category: string }> = [];
77
85
 
78
- for (const [varName, locations] of usedVars.entries()) {
86
+ for (const [varName, usage] of usedVars.entries()) {
79
87
  if (!serverlessVars.has(varName)) {
80
88
  // In non-strict mode, skip known runtime variables and custom ignore vars
81
89
  const isCustomIgnored = ConfigLoader.shouldIgnoreVar(varName, config);
@@ -87,17 +95,18 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
87
95
  skippedRuntimeVars.push({ varName, category });
88
96
  }
89
97
  } else {
90
- missingFromServerless.push({ varName, locations });
98
+ missingFromServerless.push({ varName, locations: usage.locations, hasFallback: usage.hasFallback });
91
99
  }
92
100
  }
93
101
  }
94
102
 
95
103
  if (unusedServerlessVars.length > 0) {
96
- Logger.warning('Unused variables in serverless.yml:', true);
104
+ Logger.info('Unused variables in serverless.yml:', true);
97
105
  unusedServerlessVars.forEach((varName) => {
98
- Logger.warningItem(varName, 2);
106
+ Logger.infoItem(varName, 2);
99
107
  allIssues.push({
100
108
  type: 'unused',
109
+ severity: 'info',
101
110
  varName,
102
111
  details: `Defined in serverless.yml but never used in code`,
103
112
  });
@@ -106,20 +115,48 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
106
115
  }
107
116
 
108
117
  if (missingFromServerless.length > 0) {
109
- Logger.error('Missing from serverless.yml:', true);
110
- missingFromServerless.forEach((item) => {
111
- Logger.errorItem(item.varName, 2);
112
- if (item.locations && item.locations.length > 0) {
113
- Logger.info(`Used in: ${item.locations.slice(0, 2).join(', ')}`, true);
114
- }
115
- allIssues.push({
116
- type: 'missing',
117
- varName: item.varName,
118
- details: `Used in code but not defined in serverless.yml`,
119
- locations: item.locations,
118
+ // Group by severity (respect detectFallbacks config)
119
+ const errors = missingFromServerless.filter(item => !detectFallbacks || !item.hasFallback);
120
+ const warnings = missingFromServerless.filter(item => detectFallbacks && item.hasFallback);
121
+
122
+ if (errors.length > 0) {
123
+ Logger.error('Missing from serverless.yml:', true);
124
+ errors.forEach((item) => {
125
+ Logger.errorItem(item.varName, 2);
126
+ if (item.locations && item.locations.length > 0) {
127
+ Logger.info(`Used in: ${item.locations.slice(0, 2).join(', ')}`, true);
128
+ }
129
+ const details = (detectFallbacks && item.hasFallback)
130
+ ? `Used in code with fallback but not defined in serverless.yml`
131
+ : `Used in code but not defined in serverless.yml`;
132
+ allIssues.push({
133
+ type: 'missing',
134
+ severity: 'error',
135
+ varName: item.varName,
136
+ details,
137
+ locations: item.locations,
138
+ });
120
139
  });
121
- });
122
- Logger.blank();
140
+ Logger.blank();
141
+ }
142
+
143
+ if (warnings.length > 0) {
144
+ Logger.warning('Missing from serverless.yml (with fallback):', true);
145
+ warnings.forEach((item) => {
146
+ Logger.warningItem(item.varName, 2);
147
+ if (item.locations && item.locations.length > 0) {
148
+ Logger.info(`Used in: ${item.locations.slice(0, 2).join(', ')}`, true);
149
+ }
150
+ allIssues.push({
151
+ type: 'missing',
152
+ severity: 'warning',
153
+ varName: item.varName,
154
+ details: `Used in code with fallback but not defined in serverless.yml`,
155
+ locations: item.locations,
156
+ });
157
+ });
158
+ Logger.blank();
159
+ }
123
160
  }
124
161
 
125
162
  if (unusedServerlessVars.length === 0 && missingFromServerless.length === 0) {
@@ -155,7 +192,26 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
155
192
  Logger.blank();
156
193
 
157
194
  // Step 3: Scan code files in this directory and subdirectories
158
- const usedVars = await scanDirectoryForVars(rootDir, envDir, scanner);
195
+ const allUsedVars = await scanDirectoryForVars(rootDir, envDir, scanner, config.exclude);
196
+
197
+ // Filter out ignored variables based on config
198
+ const usedVars = new Map<string, { locations: string[], hasFallback: boolean }>();
199
+ const skippedVarsInScope: Array<{ varName: string; category: string }> = [];
200
+
201
+ for (const [varName, usage] of allUsedVars.entries()) {
202
+ const isCustomIgnored = ConfigLoader.shouldIgnoreVar(varName, config);
203
+ const isRuntimeVar = isKnownRuntimeVar(varName);
204
+
205
+ // In non-strict mode, skip known runtime variables and custom ignore vars
206
+ if (strictMode || (!isRuntimeVar && !isCustomIgnored)) {
207
+ usedVars.set(varName, usage);
208
+ } else {
209
+ const category = isCustomIgnored ? 'Custom (from config)' : getRuntimeVarCategory(varName);
210
+ if (category) {
211
+ skippedVarsInScope.push({ varName, category });
212
+ }
213
+ }
214
+ }
159
215
 
160
216
  Logger.info(`Found ${usedVars.size} variable(s) used in this scope`, true);
161
217
 
@@ -170,17 +226,19 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
170
226
  Logger.blank();
171
227
 
172
228
  // Step 6: Analyze and find issues
173
- const result = analyzer.analyze(usedVars, definedVars, exampleVars);
229
+ const result = analyzer.analyze(usedVars, definedVars, exampleVars, detectFallbacks);
174
230
 
175
231
  if (result.issues.length > 0) {
176
- // Group issues by type
177
- const missingIssues = result.issues.filter(i => i.type === 'missing');
232
+ // Group issues by type and severity
233
+ const missingErrors = result.issues.filter(i => i.type === 'missing' && i.severity === 'error');
234
+ const missingWarnings = result.issues.filter(i => i.type === 'missing' && i.severity === 'warning');
178
235
  const unusedIssues = result.issues.filter(i => i.type === 'unused');
179
- const undocumentedIssues = result.issues.filter(i => i.type === 'undocumented');
236
+ const undocumentedWarnings = result.issues.filter(i => i.type === 'undocumented' && i.severity === 'warning');
237
+ const undocumentedInfo = result.issues.filter(i => i.type === 'undocumented' && i.severity === 'info');
180
238
 
181
- if (missingIssues.length > 0) {
239
+ if (missingErrors.length > 0) {
182
240
  Logger.error('Missing from .env:', true);
183
- missingIssues.forEach((issue) => {
241
+ missingErrors.forEach((issue) => {
184
242
  Logger.errorItem(issue.varName, 2);
185
243
  if (issue.locations && issue.locations.length > 0) {
186
244
  Logger.info(`Used in: ${issue.locations.slice(0, 2).join(', ')}`, true);
@@ -189,17 +247,36 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
189
247
  Logger.blank();
190
248
  }
191
249
 
250
+ if (missingWarnings.length > 0) {
251
+ Logger.warning('Missing from .env (with fallback):', true);
252
+ missingWarnings.forEach((issue) => {
253
+ Logger.warningItem(issue.varName, 2);
254
+ if (issue.locations && issue.locations.length > 0) {
255
+ Logger.info(`Used in: ${issue.locations.slice(0, 2).join(', ')}`, true);
256
+ }
257
+ });
258
+ Logger.blank();
259
+ }
260
+
192
261
  if (unusedIssues.length > 0) {
193
- Logger.warning('Unused variables:', true);
262
+ Logger.info('Unused variables:', true);
194
263
  unusedIssues.forEach((issue) => {
264
+ Logger.infoItem(issue.varName, 2);
265
+ });
266
+ Logger.blank();
267
+ }
268
+
269
+ if (undocumentedWarnings.length > 0) {
270
+ Logger.warning('Missing from .env.example:', true);
271
+ undocumentedWarnings.forEach((issue) => {
195
272
  Logger.warningItem(issue.varName, 2);
196
273
  });
197
274
  Logger.blank();
198
275
  }
199
276
 
200
- if (undocumentedIssues.length > 0) {
201
- Logger.info('Missing from .env.example:', true);
202
- undocumentedIssues.forEach((issue) => {
277
+ if (undocumentedInfo.length > 0) {
278
+ Logger.info('Missing from .env.example (with fallback):', true);
279
+ undocumentedInfo.forEach((issue) => {
203
280
  Logger.infoItem(issue.varName, 2);
204
281
  });
205
282
  Logger.blank();
@@ -210,6 +287,23 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
210
287
  Logger.success('No issues in this directory', true);
211
288
  Logger.blank();
212
289
  }
290
+
291
+ // Show skipped variables in non-strict mode
292
+ if (!strictMode && skippedVarsInScope.length > 0) {
293
+ Logger.info('Skipped known runtime/ignored variables (use --strict to show):', true);
294
+ // Group by category
295
+ const grouped = new Map<string, string[]>();
296
+ for (const { varName, category } of skippedVarsInScope) {
297
+ if (!grouped.has(category)) {
298
+ grouped.set(category, []);
299
+ }
300
+ grouped.get(category)!.push(varName);
301
+ }
302
+ for (const [category, vars] of grouped.entries()) {
303
+ Logger.info(`${category}: ${vars.join(', ')}`, true);
304
+ }
305
+ Logger.blank();
306
+ }
213
307
  }
214
308
 
215
309
  // Display summary
@@ -219,8 +313,23 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
219
313
  return { success: true, issues: [] };
220
314
  }
221
315
 
316
+ // Count issues by severity
317
+ const errorCount = allIssues.filter(i => i.severity === 'error').length;
318
+ const warningCount = allIssues.filter(i => i.severity === 'warning').length;
319
+ const infoCount = allIssues.filter(i => i.severity === 'info').length;
320
+
222
321
  Logger.blank();
223
- Logger.warning(`Total: ${allIssues.length} issue(s) across ${envFiles.length} location(s)`);
322
+ if (errorCount > 0) {
323
+ Logger.error(`Errors: ${errorCount}`, false);
324
+ }
325
+ if (warningCount > 0) {
326
+ Logger.warning(`Warnings: ${warningCount}`, false);
327
+ }
328
+ if (infoCount > 0) {
329
+ Logger.info(`Info: ${infoCount}`, false);
330
+ }
331
+ Logger.blank();
332
+ Logger.warning(`Total: ${allIssues.length} issue(s) across ${envFiles.length + serverlessFiles.length} location(s)`);
224
333
  Logger.blank();
225
334
 
226
335
  // Suggest fix
@@ -242,28 +351,34 @@ export async function scanCommand(options: { ci?: boolean; strict?: boolean }) {
242
351
  async function scanDirectoryForVars(
243
352
  rootDir: string,
244
353
  targetDir: string,
245
- scanner: CodeScanner
246
- ): Promise<Map<string, string[]>> {
247
- const envVars = new Map<string, string[]>();
354
+ scanner: CodeScanner,
355
+ excludePatterns: string[] = []
356
+ ): Promise<Map<string, { locations: string[], hasFallback: boolean }>> {
357
+ const envVars = new Map<string, { locations: string[], hasFallback: boolean }>();
248
358
 
249
359
  // Find all code files in this directory and subdirectories
250
360
  const relativeDir = path.relative(rootDir, targetDir);
251
361
  const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
252
362
 
363
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
364
+ const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
365
+
253
366
  const files = await glob(pattern, {
254
367
  cwd: rootDir,
255
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
368
+ ignore: [...defaultIgnore, ...customIgnore],
256
369
  absolute: true,
257
370
  });
258
371
 
259
372
  for (const file of files) {
260
373
  const vars = await scanner.scanFile(file);
261
- for (const varName of vars) {
374
+ for (const [varName, hasFallback] of vars.entries()) {
262
375
  const relativePath = path.relative(rootDir, file);
263
376
  if (!envVars.has(varName)) {
264
- envVars.set(varName, []);
377
+ envVars.set(varName, { locations: [], hasFallback: false });
265
378
  }
266
- envVars.get(varName)!.push(relativePath);
379
+ const entry = envVars.get(varName)!;
380
+ entry.locations.push(relativePath);
381
+ entry.hasFallback = entry.hasFallback || hasFallback;
267
382
  }
268
383
  }
269
384
 
@@ -274,28 +389,34 @@ async function scanDirectoryForVars(
274
389
  async function scanDirectoryForCodeVars(
275
390
  rootDir: string,
276
391
  targetDir: string,
277
- scanner: CodeScanner
278
- ): Promise<Map<string, string[]>> {
279
- const envVars = new Map<string, string[]>();
392
+ scanner: CodeScanner,
393
+ excludePatterns: string[] = []
394
+ ): Promise<Map<string, { locations: string[], hasFallback: boolean }>> {
395
+ const envVars = new Map<string, { locations: string[], hasFallback: boolean }>();
280
396
 
281
397
  // Find all code files in this directory only (not subdirectories for serverless)
282
398
  const relativeDir = path.relative(rootDir, targetDir);
283
399
  const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
284
400
 
401
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
402
+ const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
403
+
285
404
  const files = await glob(pattern, {
286
405
  cwd: rootDir,
287
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
406
+ ignore: [...defaultIgnore, ...customIgnore],
288
407
  absolute: true,
289
408
  });
290
409
 
291
410
  for (const file of files) {
292
411
  const vars = await scanner.scanFile(file);
293
- for (const varName of vars) {
412
+ for (const [varName, hasFallback] of vars.entries()) {
294
413
  const relativePath = path.relative(rootDir, file);
295
414
  if (!envVars.has(varName)) {
296
- envVars.set(varName, []);
415
+ envVars.set(varName, { locations: [], hasFallback: false });
297
416
  }
298
- envVars.get(varName)!.push(relativePath);
417
+ const entry = envVars.get(varName)!;
418
+ entry.locations.push(relativePath);
419
+ entry.hasFallback = entry.hasFallback || hasFallback;
299
420
  }
300
421
  }
301
422