@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.
- package/.envguardrc.example.json +1 -0
- package/.envguardrc.json +16 -0
- package/.github/FUNDING.yml +15 -0
- package/LICENSE +1 -1
- package/README.md +246 -11
- package/dist/analyzer/envAnalyzer.d.ts +8 -2
- package/dist/analyzer/envAnalyzer.d.ts.map +1 -1
- package/dist/analyzer/envAnalyzer.js +22 -8
- package/dist/analyzer/envAnalyzer.js.map +1 -1
- package/dist/cli.js +25 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +32 -7
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/scan.d.ts +1 -0
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +150 -41
- package/dist/commands/scan.js.map +1 -1
- package/dist/config/configLoader.d.ts +6 -0
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +1 -0
- package/dist/config/configLoader.js.map +1 -1
- package/dist/scanner/codeScanner.d.ts +5 -2
- package/dist/scanner/codeScanner.d.ts.map +1 -1
- package/dist/scanner/codeScanner.js +72 -25
- package/dist/scanner/codeScanner.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/analyzer/envAnalyzer.ts +27 -10
- package/src/cli.ts +29 -3
- package/src/commands/fix.ts +40 -9
- package/src/commands/scan.ts +168 -47
- package/src/config/configLoader.ts +8 -0
- package/src/scanner/codeScanner.ts +97 -28
- package/src/types.ts +3 -1
- package/test-project/src/lambda2/handler2.js +1 -1
- package/test-project/.envguardrc.json +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codeScanner.js","sourceRoot":"","sources":["../../src/scanner/codeScanner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAC7B,+BAA4B;AAC5B,iEAA8D;
|
|
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,
|
|
22
|
+
usedVars: Map<string, {
|
|
23
|
+
locations: string[];
|
|
24
|
+
hasFallback: boolean;
|
|
25
|
+
}>;
|
|
21
26
|
definedVars: Set<string>;
|
|
22
27
|
exampleVars: Set<string>;
|
|
23
28
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
@@ -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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
.
|
|
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
|
-
.
|
|
60
|
+
.option('--no-detect-fallbacks', 'Treat all missing variables as errors, ignoring fallback detection')
|
|
61
|
+
.action(async (cmd, command) => {
|
|
47
62
|
try {
|
|
48
|
-
|
|
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);
|
package/src/commands/fix.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
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: [
|
|
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)
|
|
136
|
+
const entry = envVars.get(varName)!;
|
|
137
|
+
entry.locations.push(relativePath);
|
|
138
|
+
entry.hasFallback = entry.hasFallback || hasFallback;
|
|
108
139
|
}
|
|
109
140
|
}
|
|
110
141
|
|
package/src/commands/scan.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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.
|
|
104
|
+
Logger.info('Unused variables in serverless.yml:', true);
|
|
97
105
|
unusedServerlessVars.forEach((varName) => {
|
|
98
|
-
Logger.
|
|
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
|
-
|
|
110
|
-
missingFromServerless.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
239
|
+
if (missingErrors.length > 0) {
|
|
182
240
|
Logger.error('Missing from .env:', true);
|
|
183
|
-
|
|
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.
|
|
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 (
|
|
201
|
-
Logger.info('Missing from .env.example:', true);
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
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: [
|
|
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)
|
|
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
|
-
|
|
279
|
-
|
|
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: [
|
|
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)
|
|
417
|
+
const entry = envVars.get(varName)!;
|
|
418
|
+
entry.locations.push(relativePath);
|
|
419
|
+
entry.hasFallback = entry.hasFallback || hasFallback;
|
|
299
420
|
}
|
|
300
421
|
}
|
|
301
422
|
|