@danielszlaski/envguard 0.1.3 → 0.1.5
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 +285 -10
- 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 +58 -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/install-hook.d.ts +18 -0
- package/dist/commands/install-hook.d.ts.map +1 -0
- package/dist/commands/install-hook.js +148 -0
- package/dist/commands/install-hook.js.map +1 -0
- 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 +62 -3
- package/src/commands/fix.ts +40 -9
- package/src/commands/install-hook.ts +128 -0
- 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
package/dist/commands/fix.js
CHANGED
|
@@ -43,8 +43,18 @@ const logger_1 = require("../utils/logger");
|
|
|
43
43
|
async function fixCommand() {
|
|
44
44
|
const rootDir = process.cwd();
|
|
45
45
|
logger_1.Logger.startSpinner('Generating .env.example files...');
|
|
46
|
+
// Load configuration
|
|
47
|
+
const { ConfigLoader } = require('../config/configLoader');
|
|
48
|
+
const config = ConfigLoader.loadConfig(rootDir);
|
|
46
49
|
// Step 1: Find all .env files
|
|
47
|
-
const
|
|
50
|
+
const excludePatterns = [
|
|
51
|
+
'node_modules',
|
|
52
|
+
'dist',
|
|
53
|
+
'build',
|
|
54
|
+
'.git',
|
|
55
|
+
...(config.exclude || []),
|
|
56
|
+
];
|
|
57
|
+
const scanner = new codeScanner_1.CodeScanner(rootDir, excludePatterns);
|
|
48
58
|
const envFiles = await scanner.findEnvFiles();
|
|
49
59
|
logger_1.Logger.stopSpinner();
|
|
50
60
|
if (envFiles.length === 0) {
|
|
@@ -64,7 +74,18 @@ async function fixCommand() {
|
|
|
64
74
|
const displayPath = relativePath || '.';
|
|
65
75
|
logger_1.Logger.path(`Processing ${displayPath}/`);
|
|
66
76
|
// Step 3: Scan code files in this directory and subdirectories
|
|
67
|
-
const
|
|
77
|
+
const allUsedVars = await scanDirectoryForVars(rootDir, envDir, scanner, config.exclude);
|
|
78
|
+
// Filter out ignored variables based on config (they shouldn't be in .env.example)
|
|
79
|
+
const { isKnownRuntimeVar } = require('../constants/knownEnvVars');
|
|
80
|
+
const usedVars = new Map();
|
|
81
|
+
for (const [varName, usage] of allUsedVars.entries()) {
|
|
82
|
+
const isCustomIgnored = ConfigLoader.shouldIgnoreVar(varName, config);
|
|
83
|
+
const isRuntimeVar = isKnownRuntimeVar(varName);
|
|
84
|
+
// Skip known runtime variables and custom ignore vars
|
|
85
|
+
if (!isRuntimeVar && !isCustomIgnored) {
|
|
86
|
+
usedVars.set(varName, usage);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
68
89
|
if (usedVars.size === 0) {
|
|
69
90
|
logger_1.Logger.info('No environment variables found in code', true);
|
|
70
91
|
logger_1.Logger.blank();
|
|
@@ -93,25 +114,29 @@ async function fixCommand() {
|
|
|
93
114
|
logger_1.Logger.summary(`Generated ${envFiles.length} .env.example file(s) with ${totalVars} total variables`);
|
|
94
115
|
return { success: true };
|
|
95
116
|
}
|
|
96
|
-
async function scanDirectoryForVars(rootDir, targetDir, scanner) {
|
|
117
|
+
async function scanDirectoryForVars(rootDir, targetDir, scanner, excludePatterns = []) {
|
|
97
118
|
const envVars = new Map();
|
|
98
119
|
const { glob } = require('glob');
|
|
99
120
|
// Find all code files in this directory and subdirectories
|
|
100
121
|
const relativeDir = path.relative(rootDir, targetDir);
|
|
101
122
|
const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
|
|
123
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
|
|
124
|
+
const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
|
|
102
125
|
const files = await glob(pattern, {
|
|
103
126
|
cwd: rootDir,
|
|
104
|
-
ignore: [
|
|
127
|
+
ignore: [...defaultIgnore, ...customIgnore],
|
|
105
128
|
absolute: true,
|
|
106
129
|
});
|
|
107
130
|
for (const file of files) {
|
|
108
131
|
const vars = await scanner.scanFile(file);
|
|
109
|
-
for (const varName of vars) {
|
|
132
|
+
for (const [varName, hasFallback] of vars.entries()) {
|
|
110
133
|
const relativePath = path.relative(rootDir, file);
|
|
111
134
|
if (!envVars.has(varName)) {
|
|
112
|
-
envVars.set(varName, []);
|
|
135
|
+
envVars.set(varName, { locations: [], hasFallback: false });
|
|
113
136
|
}
|
|
114
|
-
envVars.get(varName)
|
|
137
|
+
const entry = envVars.get(varName);
|
|
138
|
+
entry.locations.push(relativePath);
|
|
139
|
+
entry.hasFallback = entry.hasFallback || hasFallback;
|
|
115
140
|
}
|
|
116
141
|
}
|
|
117
142
|
return envVars;
|
package/dist/commands/fix.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,
|
|
1
|
+
{"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare const HOOK_TYPES: readonly ["pre-commit", "pre-push"];
|
|
2
|
+
type HookType = typeof HOOK_TYPES[number];
|
|
3
|
+
interface InstallHookOptions {
|
|
4
|
+
type?: HookType;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Install a Git hook that runs envguard before commits or pushes
|
|
9
|
+
*/
|
|
10
|
+
export declare function installHookCommand(options?: InstallHookOptions): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Uninstall a Git hook
|
|
13
|
+
*/
|
|
14
|
+
export declare function uninstallHookCommand(options?: {
|
|
15
|
+
type?: HookType;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=install-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-hook.d.ts","sourceRoot":"","sources":["../../src/commands/install-hook.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,UAAU,qCAAsC,CAAC;AACvD,KAAK,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAE1C,UAAU,kBAAkB;IAC1B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,iBA8CxE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAO,iBA6B3E"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.installHookCommand = installHookCommand;
|
|
37
|
+
exports.uninstallHookCommand = uninstallHookCommand;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const logger_1 = require("../utils/logger");
|
|
41
|
+
const HOOK_TYPES = ['pre-commit', 'pre-push'];
|
|
42
|
+
/**
|
|
43
|
+
* Install a Git hook that runs envguard before commits or pushes
|
|
44
|
+
*/
|
|
45
|
+
async function installHookCommand(options = {}) {
|
|
46
|
+
const rootDir = process.cwd();
|
|
47
|
+
const gitDir = path.join(rootDir, '.git');
|
|
48
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
49
|
+
// Check if this is a git repository
|
|
50
|
+
if (!fs.existsSync(gitDir)) {
|
|
51
|
+
logger_1.Logger.error('Not a git repository. Please run this command in a git repository.');
|
|
52
|
+
logger_1.Logger.blank();
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
// Ensure hooks directory exists
|
|
56
|
+
if (!fs.existsSync(hooksDir)) {
|
|
57
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
58
|
+
logger_1.Logger.success('Created .git/hooks directory');
|
|
59
|
+
}
|
|
60
|
+
const hookType = options.type || 'pre-commit';
|
|
61
|
+
const hookPath = path.join(hooksDir, hookType);
|
|
62
|
+
// Check if hook already exists
|
|
63
|
+
if (fs.existsSync(hookPath) && !options.force) {
|
|
64
|
+
logger_1.Logger.warning(`${hookType} hook already exists.`);
|
|
65
|
+
logger_1.Logger.info('Use --force to overwrite the existing hook', true);
|
|
66
|
+
logger_1.Logger.blank();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
// Create the hook script
|
|
70
|
+
const hookContent = generateHookScript(hookType);
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(hookPath, hookContent, { mode: 0o755 });
|
|
73
|
+
logger_1.Logger.success(`Installed ${hookType} hook successfully!`);
|
|
74
|
+
logger_1.Logger.blank();
|
|
75
|
+
logger_1.Logger.info('The hook will run `envguard check` automatically before each ' +
|
|
76
|
+
(hookType === 'pre-commit' ? 'commit' : 'push'), true);
|
|
77
|
+
logger_1.Logger.info('To bypass the hook, use: git ' +
|
|
78
|
+
(hookType === 'pre-commit' ? 'commit' : 'push') + ' --no-verify', true);
|
|
79
|
+
logger_1.Logger.blank();
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
logger_1.Logger.error(`Failed to install hook: ${error}`);
|
|
83
|
+
logger_1.Logger.blank();
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Uninstall a Git hook
|
|
89
|
+
*/
|
|
90
|
+
async function uninstallHookCommand(options = {}) {
|
|
91
|
+
const rootDir = process.cwd();
|
|
92
|
+
const hookType = options.type || 'pre-commit';
|
|
93
|
+
const hookPath = path.join(rootDir, '.git', 'hooks', hookType);
|
|
94
|
+
if (!fs.existsSync(hookPath)) {
|
|
95
|
+
logger_1.Logger.warning(`No ${hookType} hook found.`);
|
|
96
|
+
logger_1.Logger.blank();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Check if it's our hook
|
|
100
|
+
const hookContent = fs.readFileSync(hookPath, 'utf-8');
|
|
101
|
+
if (!hookContent.includes('envguard check')) {
|
|
102
|
+
logger_1.Logger.warning(`The ${hookType} hook exists but was not created by envguard.`);
|
|
103
|
+
logger_1.Logger.info('Manual removal required if you want to delete it.', true);
|
|
104
|
+
logger_1.Logger.blank();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
fs.unlinkSync(hookPath);
|
|
109
|
+
logger_1.Logger.success(`Removed ${hookType} hook successfully!`);
|
|
110
|
+
logger_1.Logger.blank();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
logger_1.Logger.error(`Failed to remove hook: ${error}`);
|
|
114
|
+
logger_1.Logger.blank();
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Generate the hook script content
|
|
120
|
+
*/
|
|
121
|
+
function generateHookScript(hookType) {
|
|
122
|
+
const hookMessage = hookType === 'pre-commit' ? 'commit' : 'push';
|
|
123
|
+
return `#!/bin/sh
|
|
124
|
+
# EnvGuard ${hookType} hook
|
|
125
|
+
# This hook runs envguard to check environment variables before ${hookMessage}
|
|
126
|
+
# To bypass this hook, use: git ${hookMessage} --no-verify
|
|
127
|
+
|
|
128
|
+
echo "Running EnvGuard environment variable check..."
|
|
129
|
+
|
|
130
|
+
# Run envguard check
|
|
131
|
+
npx envguard check
|
|
132
|
+
|
|
133
|
+
# Capture the exit code
|
|
134
|
+
EXIT_CODE=$?
|
|
135
|
+
|
|
136
|
+
if [ $EXIT_CODE -ne 0 ]; then
|
|
137
|
+
echo ""
|
|
138
|
+
echo "❌ EnvGuard check failed. Please fix the issues above before ${hookMessage}ing."
|
|
139
|
+
echo " Or run: git ${hookMessage} --no-verify to bypass this check."
|
|
140
|
+
echo ""
|
|
141
|
+
exit 1
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo "✓ EnvGuard check passed!"
|
|
145
|
+
exit 0
|
|
146
|
+
`;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=install-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-hook.js","sourceRoot":"","sources":["../../src/commands/install-hook.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAKjC,wBAAsB,WAAW,CAAC,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE;;;
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAKjC,wBAAsB,WAAW,CAAC,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE;;;GAgVvG"}
|
package/dist/commands/scan.js
CHANGED
|
@@ -49,9 +49,17 @@ async function scanCommand(options) {
|
|
|
49
49
|
const config = configLoader_1.ConfigLoader.loadConfig(rootDir);
|
|
50
50
|
// CLI options override config file
|
|
51
51
|
const strictMode = options.strict !== undefined ? options.strict : config.strict;
|
|
52
|
+
const detectFallbacks = options.detectFallbacks !== undefined ? options.detectFallbacks : (config.detectFallbacks !== undefined ? config.detectFallbacks : true);
|
|
52
53
|
logger_1.Logger.startSpinner('Scanning codebase for environment variables...');
|
|
53
54
|
// Step 1: Find all .env files and serverless.yml files
|
|
54
|
-
const
|
|
55
|
+
const excludePatterns = [
|
|
56
|
+
'node_modules',
|
|
57
|
+
'dist',
|
|
58
|
+
'build',
|
|
59
|
+
'.git',
|
|
60
|
+
...(config.exclude || []),
|
|
61
|
+
];
|
|
62
|
+
const scanner = new codeScanner_1.CodeScanner(rootDir, excludePatterns);
|
|
55
63
|
const envFiles = await scanner.findEnvFiles();
|
|
56
64
|
const serverlessFiles = await scanner.findServerlessFiles();
|
|
57
65
|
logger_1.Logger.stopSpinner();
|
|
@@ -76,7 +84,7 @@ async function scanCommand(options) {
|
|
|
76
84
|
const serverlessVars = serverlessParser.parse(serverlessFilePath);
|
|
77
85
|
logger_1.Logger.info(`Found ${serverlessVars.size} variable(s) in serverless.yml`, true);
|
|
78
86
|
// Scan code files in this directory to see what's actually used
|
|
79
|
-
const usedVars = await scanDirectoryForCodeVars(rootDir, serverlessDir, scanner);
|
|
87
|
+
const usedVars = await scanDirectoryForCodeVars(rootDir, serverlessDir, scanner, config.exclude);
|
|
80
88
|
logger_1.Logger.info(`Found ${usedVars.size} variable(s) used in code`, true);
|
|
81
89
|
logger_1.Logger.blank();
|
|
82
90
|
// Check for unused variables in serverless.yml
|
|
@@ -93,7 +101,7 @@ async function scanCommand(options) {
|
|
|
93
101
|
// Check for variables used in code but not defined in serverless.yml
|
|
94
102
|
const missingFromServerless = [];
|
|
95
103
|
const skippedRuntimeVars = [];
|
|
96
|
-
for (const [varName,
|
|
104
|
+
for (const [varName, usage] of usedVars.entries()) {
|
|
97
105
|
if (!serverlessVars.has(varName)) {
|
|
98
106
|
// In non-strict mode, skip known runtime variables and custom ignore vars
|
|
99
107
|
const isCustomIgnored = configLoader_1.ConfigLoader.shouldIgnoreVar(varName, config);
|
|
@@ -105,16 +113,17 @@ async function scanCommand(options) {
|
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
else {
|
|
108
|
-
missingFromServerless.push({ varName, locations });
|
|
116
|
+
missingFromServerless.push({ varName, locations: usage.locations, hasFallback: usage.hasFallback });
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
if (unusedServerlessVars.length > 0) {
|
|
113
|
-
logger_1.Logger.
|
|
121
|
+
logger_1.Logger.info('Unused variables in serverless.yml:', true);
|
|
114
122
|
unusedServerlessVars.forEach((varName) => {
|
|
115
|
-
logger_1.Logger.
|
|
123
|
+
logger_1.Logger.infoItem(varName, 2);
|
|
116
124
|
allIssues.push({
|
|
117
125
|
type: 'unused',
|
|
126
|
+
severity: 'info',
|
|
118
127
|
varName,
|
|
119
128
|
details: `Defined in serverless.yml but never used in code`,
|
|
120
129
|
});
|
|
@@ -122,20 +131,46 @@ async function scanCommand(options) {
|
|
|
122
131
|
logger_1.Logger.blank();
|
|
123
132
|
}
|
|
124
133
|
if (missingFromServerless.length > 0) {
|
|
125
|
-
|
|
126
|
-
missingFromServerless.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
// Group by severity (respect detectFallbacks config)
|
|
135
|
+
const errors = missingFromServerless.filter(item => !detectFallbacks || !item.hasFallback);
|
|
136
|
+
const warnings = missingFromServerless.filter(item => detectFallbacks && item.hasFallback);
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
logger_1.Logger.error('Missing from serverless.yml:', true);
|
|
139
|
+
errors.forEach((item) => {
|
|
140
|
+
logger_1.Logger.errorItem(item.varName, 2);
|
|
141
|
+
if (item.locations && item.locations.length > 0) {
|
|
142
|
+
logger_1.Logger.info(`Used in: ${item.locations.slice(0, 2).join(', ')}`, true);
|
|
143
|
+
}
|
|
144
|
+
const details = (detectFallbacks && item.hasFallback)
|
|
145
|
+
? `Used in code with fallback but not defined in serverless.yml`
|
|
146
|
+
: `Used in code but not defined in serverless.yml`;
|
|
147
|
+
allIssues.push({
|
|
148
|
+
type: 'missing',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
varName: item.varName,
|
|
151
|
+
details,
|
|
152
|
+
locations: item.locations,
|
|
153
|
+
});
|
|
136
154
|
});
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
logger_1.Logger.blank();
|
|
156
|
+
}
|
|
157
|
+
if (warnings.length > 0) {
|
|
158
|
+
logger_1.Logger.warning('Missing from serverless.yml (with fallback):', true);
|
|
159
|
+
warnings.forEach((item) => {
|
|
160
|
+
logger_1.Logger.warningItem(item.varName, 2);
|
|
161
|
+
if (item.locations && item.locations.length > 0) {
|
|
162
|
+
logger_1.Logger.info(`Used in: ${item.locations.slice(0, 2).join(', ')}`, true);
|
|
163
|
+
}
|
|
164
|
+
allIssues.push({
|
|
165
|
+
type: 'missing',
|
|
166
|
+
severity: 'warning',
|
|
167
|
+
varName: item.varName,
|
|
168
|
+
details: `Used in code with fallback but not defined in serverless.yml`,
|
|
169
|
+
locations: item.locations,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
logger_1.Logger.blank();
|
|
173
|
+
}
|
|
139
174
|
}
|
|
140
175
|
if (unusedServerlessVars.length === 0 && missingFromServerless.length === 0) {
|
|
141
176
|
logger_1.Logger.success('No issues in this serverless.yml', true);
|
|
@@ -166,7 +201,24 @@ async function scanCommand(options) {
|
|
|
166
201
|
logger_1.Logger.path(`Checking ${displayPath}/`);
|
|
167
202
|
logger_1.Logger.blank();
|
|
168
203
|
// Step 3: Scan code files in this directory and subdirectories
|
|
169
|
-
const
|
|
204
|
+
const allUsedVars = await scanDirectoryForVars(rootDir, envDir, scanner, config.exclude);
|
|
205
|
+
// Filter out ignored variables based on config
|
|
206
|
+
const usedVars = new Map();
|
|
207
|
+
const skippedVarsInScope = [];
|
|
208
|
+
for (const [varName, usage] of allUsedVars.entries()) {
|
|
209
|
+
const isCustomIgnored = configLoader_1.ConfigLoader.shouldIgnoreVar(varName, config);
|
|
210
|
+
const isRuntimeVar = (0, knownEnvVars_1.isKnownRuntimeVar)(varName);
|
|
211
|
+
// In non-strict mode, skip known runtime variables and custom ignore vars
|
|
212
|
+
if (strictMode || (!isRuntimeVar && !isCustomIgnored)) {
|
|
213
|
+
usedVars.set(varName, usage);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const category = isCustomIgnored ? 'Custom (from config)' : (0, knownEnvVars_1.getRuntimeVarCategory)(varName);
|
|
217
|
+
if (category) {
|
|
218
|
+
skippedVarsInScope.push({ varName, category });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
170
222
|
logger_1.Logger.info(`Found ${usedVars.size} variable(s) used in this scope`, true);
|
|
171
223
|
// Step 4: Parse .env file
|
|
172
224
|
const definedVars = parser.parse(envFilePath);
|
|
@@ -177,15 +229,17 @@ async function scanCommand(options) {
|
|
|
177
229
|
logger_1.Logger.info(`Found ${exampleVars.size} variable(s) in .env.example`, true);
|
|
178
230
|
logger_1.Logger.blank();
|
|
179
231
|
// Step 6: Analyze and find issues
|
|
180
|
-
const result = analyzer.analyze(usedVars, definedVars, exampleVars);
|
|
232
|
+
const result = analyzer.analyze(usedVars, definedVars, exampleVars, detectFallbacks);
|
|
181
233
|
if (result.issues.length > 0) {
|
|
182
|
-
// Group issues by type
|
|
183
|
-
const
|
|
234
|
+
// Group issues by type and severity
|
|
235
|
+
const missingErrors = result.issues.filter(i => i.type === 'missing' && i.severity === 'error');
|
|
236
|
+
const missingWarnings = result.issues.filter(i => i.type === 'missing' && i.severity === 'warning');
|
|
184
237
|
const unusedIssues = result.issues.filter(i => i.type === 'unused');
|
|
185
|
-
const
|
|
186
|
-
|
|
238
|
+
const undocumentedWarnings = result.issues.filter(i => i.type === 'undocumented' && i.severity === 'warning');
|
|
239
|
+
const undocumentedInfo = result.issues.filter(i => i.type === 'undocumented' && i.severity === 'info');
|
|
240
|
+
if (missingErrors.length > 0) {
|
|
187
241
|
logger_1.Logger.error('Missing from .env:', true);
|
|
188
|
-
|
|
242
|
+
missingErrors.forEach((issue) => {
|
|
189
243
|
logger_1.Logger.errorItem(issue.varName, 2);
|
|
190
244
|
if (issue.locations && issue.locations.length > 0) {
|
|
191
245
|
logger_1.Logger.info(`Used in: ${issue.locations.slice(0, 2).join(', ')}`, true);
|
|
@@ -193,16 +247,33 @@ async function scanCommand(options) {
|
|
|
193
247
|
});
|
|
194
248
|
logger_1.Logger.blank();
|
|
195
249
|
}
|
|
250
|
+
if (missingWarnings.length > 0) {
|
|
251
|
+
logger_1.Logger.warning('Missing from .env (with fallback):', true);
|
|
252
|
+
missingWarnings.forEach((issue) => {
|
|
253
|
+
logger_1.Logger.warningItem(issue.varName, 2);
|
|
254
|
+
if (issue.locations && issue.locations.length > 0) {
|
|
255
|
+
logger_1.Logger.info(`Used in: ${issue.locations.slice(0, 2).join(', ')}`, true);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
logger_1.Logger.blank();
|
|
259
|
+
}
|
|
196
260
|
if (unusedIssues.length > 0) {
|
|
197
|
-
logger_1.Logger.
|
|
261
|
+
logger_1.Logger.info('Unused variables:', true);
|
|
198
262
|
unusedIssues.forEach((issue) => {
|
|
263
|
+
logger_1.Logger.infoItem(issue.varName, 2);
|
|
264
|
+
});
|
|
265
|
+
logger_1.Logger.blank();
|
|
266
|
+
}
|
|
267
|
+
if (undocumentedWarnings.length > 0) {
|
|
268
|
+
logger_1.Logger.warning('Missing from .env.example:', true);
|
|
269
|
+
undocumentedWarnings.forEach((issue) => {
|
|
199
270
|
logger_1.Logger.warningItem(issue.varName, 2);
|
|
200
271
|
});
|
|
201
272
|
logger_1.Logger.blank();
|
|
202
273
|
}
|
|
203
|
-
if (
|
|
204
|
-
logger_1.Logger.info('Missing from .env.example:', true);
|
|
205
|
-
|
|
274
|
+
if (undocumentedInfo.length > 0) {
|
|
275
|
+
logger_1.Logger.info('Missing from .env.example (with fallback):', true);
|
|
276
|
+
undocumentedInfo.forEach((issue) => {
|
|
206
277
|
logger_1.Logger.infoItem(issue.varName, 2);
|
|
207
278
|
});
|
|
208
279
|
logger_1.Logger.blank();
|
|
@@ -213,6 +284,22 @@ async function scanCommand(options) {
|
|
|
213
284
|
logger_1.Logger.success('No issues in this directory', true);
|
|
214
285
|
logger_1.Logger.blank();
|
|
215
286
|
}
|
|
287
|
+
// Show skipped variables in non-strict mode
|
|
288
|
+
if (!strictMode && skippedVarsInScope.length > 0) {
|
|
289
|
+
logger_1.Logger.info('Skipped known runtime/ignored variables (use --strict to show):', true);
|
|
290
|
+
// Group by category
|
|
291
|
+
const grouped = new Map();
|
|
292
|
+
for (const { varName, category } of skippedVarsInScope) {
|
|
293
|
+
if (!grouped.has(category)) {
|
|
294
|
+
grouped.set(category, []);
|
|
295
|
+
}
|
|
296
|
+
grouped.get(category).push(varName);
|
|
297
|
+
}
|
|
298
|
+
for (const [category, vars] of grouped.entries()) {
|
|
299
|
+
logger_1.Logger.info(`${category}: ${vars.join(', ')}`, true);
|
|
300
|
+
}
|
|
301
|
+
logger_1.Logger.blank();
|
|
302
|
+
}
|
|
216
303
|
}
|
|
217
304
|
// Display summary
|
|
218
305
|
logger_1.Logger.divider();
|
|
@@ -220,8 +307,22 @@ async function scanCommand(options) {
|
|
|
220
307
|
logger_1.Logger.summary('No issues found! All environment variables are in sync.');
|
|
221
308
|
return { success: true, issues: [] };
|
|
222
309
|
}
|
|
310
|
+
// Count issues by severity
|
|
311
|
+
const errorCount = allIssues.filter(i => i.severity === 'error').length;
|
|
312
|
+
const warningCount = allIssues.filter(i => i.severity === 'warning').length;
|
|
313
|
+
const infoCount = allIssues.filter(i => i.severity === 'info').length;
|
|
314
|
+
logger_1.Logger.blank();
|
|
315
|
+
if (errorCount > 0) {
|
|
316
|
+
logger_1.Logger.error(`Errors: ${errorCount}`, false);
|
|
317
|
+
}
|
|
318
|
+
if (warningCount > 0) {
|
|
319
|
+
logger_1.Logger.warning(`Warnings: ${warningCount}`, false);
|
|
320
|
+
}
|
|
321
|
+
if (infoCount > 0) {
|
|
322
|
+
logger_1.Logger.info(`Info: ${infoCount}`, false);
|
|
323
|
+
}
|
|
223
324
|
logger_1.Logger.blank();
|
|
224
|
-
logger_1.Logger.warning(`Total: ${allIssues.length} issue(s) across ${envFiles.length} location(s)`);
|
|
325
|
+
logger_1.Logger.warning(`Total: ${allIssues.length} issue(s) across ${envFiles.length + serverlessFiles.length} location(s)`);
|
|
225
326
|
logger_1.Logger.blank();
|
|
226
327
|
// Suggest fix
|
|
227
328
|
if (!options.ci) {
|
|
@@ -236,47 +337,55 @@ async function scanCommand(options) {
|
|
|
236
337
|
}
|
|
237
338
|
return { success: false, issues: allIssues };
|
|
238
339
|
}
|
|
239
|
-
async function scanDirectoryForVars(rootDir, targetDir, scanner) {
|
|
340
|
+
async function scanDirectoryForVars(rootDir, targetDir, scanner, excludePatterns = []) {
|
|
240
341
|
const envVars = new Map();
|
|
241
342
|
// Find all code files in this directory and subdirectories
|
|
242
343
|
const relativeDir = path.relative(rootDir, targetDir);
|
|
243
344
|
const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
|
|
345
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
|
|
346
|
+
const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
|
|
244
347
|
const files = await (0, glob_1.glob)(pattern, {
|
|
245
348
|
cwd: rootDir,
|
|
246
|
-
ignore: [
|
|
349
|
+
ignore: [...defaultIgnore, ...customIgnore],
|
|
247
350
|
absolute: true,
|
|
248
351
|
});
|
|
249
352
|
for (const file of files) {
|
|
250
353
|
const vars = await scanner.scanFile(file);
|
|
251
|
-
for (const varName of vars) {
|
|
354
|
+
for (const [varName, hasFallback] of vars.entries()) {
|
|
252
355
|
const relativePath = path.relative(rootDir, file);
|
|
253
356
|
if (!envVars.has(varName)) {
|
|
254
|
-
envVars.set(varName, []);
|
|
357
|
+
envVars.set(varName, { locations: [], hasFallback: false });
|
|
255
358
|
}
|
|
256
|
-
envVars.get(varName)
|
|
359
|
+
const entry = envVars.get(varName);
|
|
360
|
+
entry.locations.push(relativePath);
|
|
361
|
+
entry.hasFallback = entry.hasFallback || hasFallback;
|
|
257
362
|
}
|
|
258
363
|
}
|
|
259
364
|
return envVars;
|
|
260
365
|
}
|
|
261
366
|
// Scan only code files (JS/TS), not including serverless.yml as a source
|
|
262
|
-
async function scanDirectoryForCodeVars(rootDir, targetDir, scanner) {
|
|
367
|
+
async function scanDirectoryForCodeVars(rootDir, targetDir, scanner, excludePatterns = []) {
|
|
263
368
|
const envVars = new Map();
|
|
264
369
|
// Find all code files in this directory only (not subdirectories for serverless)
|
|
265
370
|
const relativeDir = path.relative(rootDir, targetDir);
|
|
266
371
|
const pattern = relativeDir ? `${relativeDir}/**/*.{js,ts,jsx,tsx,mjs,cjs}` : '**/*.{js,ts,jsx,tsx,mjs,cjs}';
|
|
372
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
|
|
373
|
+
const customIgnore = excludePatterns.map(p => p.includes('*') ? p : `**/${p}/**`);
|
|
267
374
|
const files = await (0, glob_1.glob)(pattern, {
|
|
268
375
|
cwd: rootDir,
|
|
269
|
-
ignore: [
|
|
376
|
+
ignore: [...defaultIgnore, ...customIgnore],
|
|
270
377
|
absolute: true,
|
|
271
378
|
});
|
|
272
379
|
for (const file of files) {
|
|
273
380
|
const vars = await scanner.scanFile(file);
|
|
274
|
-
for (const varName of vars) {
|
|
381
|
+
for (const [varName, hasFallback] of vars.entries()) {
|
|
275
382
|
const relativePath = path.relative(rootDir, file);
|
|
276
383
|
if (!envVars.has(varName)) {
|
|
277
|
-
envVars.set(varName, []);
|
|
384
|
+
envVars.set(varName, { locations: [], hasFallback: false });
|
|
278
385
|
}
|
|
279
|
-
envVars.get(varName)
|
|
386
|
+
const entry = envVars.get(varName);
|
|
387
|
+
entry.locations.push(relativePath);
|
|
388
|
+
entry.hasFallback = entry.hasFallback || hasFallback;
|
|
280
389
|
}
|
|
281
390
|
}
|
|
282
391
|
return envVars;
|