@vibecheck-ai/mcp 24.6.8 → 24.6.11
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 +1 -1
- package/dist/APITruthEngine-IZRR3NT5-LPFUOMLD.js +9 -0
- package/dist/CredentialsEngine-B66ANCBB-HY5ZQTSX.js +9 -0
- package/dist/EnvVarEngine-ZFNW2XKP-6HRTZULP.js +9 -0
- package/dist/ErrorHandlingEngine-VAHVDVFG-3GDGRN3U.js +11 -0
- package/dist/FrameworkPackEngine-RRBJW4MC-KH7WRXXS.js +12 -0
- package/dist/GhostRouteEngine-UMYBCOCL-MSZOPVZY.js +9 -0
- package/dist/LogicGapEngine-OK5UKZQ5-YGXZDERB.js +11 -0
- package/dist/PhantomDepEngine-HQEXAS25-TRVEXBMF.js +10 -0
- package/dist/SecurityEngine-MVMRPKLH-BNP7IC46.js +9 -0
- package/dist/VersionHallucinationEngine-673DJ26J-BD4SK6JX.js +9 -0
- package/dist/chokidar-CI5VJY5M.js +2414 -0
- package/dist/chunk-43XAAYST.js +863 -0
- package/dist/chunk-5DADZJ3D.js +650 -0
- package/dist/chunk-DDTUTWRY.js +605 -0
- package/dist/chunk-DGNNNAVK.js +304 -0
- package/dist/chunk-F34MHA6A.js +772 -0
- package/dist/chunk-FGMVY5QW.js +42 -0
- package/dist/chunk-G3FQJC2H.js +1968 -0
- package/dist/chunk-J52EUKKW.js +196 -0
- package/dist/chunk-JZSHXEYP.js +915 -0
- package/dist/chunk-LQSBUKYZ.js +551 -0
- package/dist/chunk-MUP4JXOF.js +219 -0
- package/dist/chunk-NR36RTVO.js +152 -0
- package/dist/chunk-QFDZMUGO.js +213300 -0
- package/dist/chunk-QGPX6H6L.js +3044 -0
- package/dist/chunk-QYXENOVK.js +499 -0
- package/dist/chunk-RNFMO5GH.js +8861 -0
- package/dist/chunk-RR5ETBSV.js +66 -0
- package/dist/chunk-YWUMPN4Z.js +53 -0
- package/dist/dist-HFMJ3GIR.js +1091 -0
- package/dist/dist-JUOVMQEA.js +9 -0
- package/dist/dist-NXITTS32-O3XLWR6T.js +386 -0
- package/dist/dist-OUJKMVTB.js +22 -0
- package/dist/fingerprint-NOJ7TDB6-K6SB7LCZ.js +9 -0
- package/dist/index.js +4735 -3577
- package/dist/semantic-WW6XVII4.js +8544 -0
- package/dist/transformers.node-K4WKH4PR.js +45809 -0
- package/dist/tree-sitter-AGICL65I.js +1412 -0
- package/dist/tree-sitter-H5E7LKR4-MKO3NNLJ.js +9 -0
- package/package.json +7 -6
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
createRequire(import.meta.url);
|
|
6
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
7
|
+
dirname(__filename$1);
|
|
8
|
+
|
|
9
|
+
// ../engines/dist/chunk-VBAMGELV.js
|
|
10
|
+
function deterministicId(uri, line, varName) {
|
|
11
|
+
const input = `env:${uri}::${line}::ENV001::${varName}`;
|
|
12
|
+
let hash = 2166136261;
|
|
13
|
+
for (let i = 0; i < input.length; i++) {
|
|
14
|
+
hash ^= input.charCodeAt(i);
|
|
15
|
+
hash = hash * 16777619 >>> 0;
|
|
16
|
+
}
|
|
17
|
+
return `env-${hash.toString(16).padStart(8, "0")}`;
|
|
18
|
+
}
|
|
19
|
+
var ENV_PATTERN = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
20
|
+
var DESTRUCTURE_PATTERN = /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*process\.env/g;
|
|
21
|
+
var BRACKET_PATTERN = /process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]\]/g;
|
|
22
|
+
var IMPORT_META_ENV_PATTERN = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
23
|
+
function hasFallback(line, matchStart, matchEnd) {
|
|
24
|
+
const rest = line.slice(matchEnd).trimStart();
|
|
25
|
+
if (/^(?:\|\||[?]{2})\s*(?!\s*(?:undefined|null|void)\b)/.test(rest)) return true;
|
|
26
|
+
if (rest.startsWith("?") && !rest.startsWith("??") && !rest.startsWith("?.")) return true;
|
|
27
|
+
const before = line.slice(0, matchStart).trimEnd();
|
|
28
|
+
if (/(?:\|\||[?]{2})\s*$/.test(before)) return true;
|
|
29
|
+
if (/\?\s*$/.test(before)) return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function isConditionalCheck(line, matchStart) {
|
|
33
|
+
const before = line.slice(0, matchStart).trimEnd();
|
|
34
|
+
if (/(?:if\s*\(|&&\s*$|\?\s*$|\|\|\s*$|!\s*$|!!\s*$)/.test(before)) return true;
|
|
35
|
+
const afterEnvVar = line.slice(matchStart).replace(/^process\.env\.[A-Z_][A-Z0-9_]*/, "").trimStart();
|
|
36
|
+
if (/^[?](?!\.)/.test(afterEnvVar) || /^(?:&&|\|\|)/.test(afterEnvVar)) return true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
var WELL_KNOWN_PREFIXES = [
|
|
40
|
+
// Frameworks
|
|
41
|
+
"NEXT_PUBLIC_",
|
|
42
|
+
"NEXT_",
|
|
43
|
+
"NEXTAUTH_",
|
|
44
|
+
"NUXT_",
|
|
45
|
+
"VITE_",
|
|
46
|
+
"GATSBY_",
|
|
47
|
+
"EXPO_PUBLIC_",
|
|
48
|
+
"REACT_APP_",
|
|
49
|
+
"VUE_APP_",
|
|
50
|
+
"PUBLIC_",
|
|
51
|
+
// Desktop / build tools
|
|
52
|
+
"ELECTRON_",
|
|
53
|
+
"TURBO_",
|
|
54
|
+
"PLASMO_",
|
|
55
|
+
"TAURI_",
|
|
56
|
+
// Hosting / CI
|
|
57
|
+
"VERCEL_",
|
|
58
|
+
"npm_",
|
|
59
|
+
"NETLIFY_",
|
|
60
|
+
"HEROKU_",
|
|
61
|
+
"RAILWAY_",
|
|
62
|
+
"RENDER_",
|
|
63
|
+
"FLY_",
|
|
64
|
+
"GITHUB_",
|
|
65
|
+
"GH_",
|
|
66
|
+
"GITLAB_",
|
|
67
|
+
"BITBUCKET_",
|
|
68
|
+
"CIRCLE_",
|
|
69
|
+
"TRAVIS_",
|
|
70
|
+
"JENKINS_",
|
|
71
|
+
"CODESPACE_",
|
|
72
|
+
"GITPOD_",
|
|
73
|
+
"REPLIT_",
|
|
74
|
+
// Cloud providers
|
|
75
|
+
"AWS_",
|
|
76
|
+
"AZURE_",
|
|
77
|
+
"GOOGLE_",
|
|
78
|
+
"GCP_",
|
|
79
|
+
"DOCKER_",
|
|
80
|
+
// Databases
|
|
81
|
+
"POSTGRES_",
|
|
82
|
+
"PG_",
|
|
83
|
+
"MYSQL_",
|
|
84
|
+
"MONGO_",
|
|
85
|
+
"MONGODB_",
|
|
86
|
+
"REDIS_",
|
|
87
|
+
// SaaS integrations
|
|
88
|
+
"STRIPE_",
|
|
89
|
+
"SUPABASE_",
|
|
90
|
+
"FIREBASE_",
|
|
91
|
+
"CLERK_",
|
|
92
|
+
"AUTH0_",
|
|
93
|
+
"RESEND_",
|
|
94
|
+
"SENDGRID_",
|
|
95
|
+
"TWILIO_",
|
|
96
|
+
"POSTMARK_",
|
|
97
|
+
"MAILGUN_",
|
|
98
|
+
"SENTRY_",
|
|
99
|
+
"DATADOG_",
|
|
100
|
+
"LOGROCKET_",
|
|
101
|
+
"SEGMENT_",
|
|
102
|
+
"POSTHOG_",
|
|
103
|
+
"ALGOLIA_",
|
|
104
|
+
"MEILISEARCH_",
|
|
105
|
+
"TYPESENSE_",
|
|
106
|
+
"CONTENTFUL_",
|
|
107
|
+
"SANITY_",
|
|
108
|
+
"STRAPI_",
|
|
109
|
+
"PRISMIC_",
|
|
110
|
+
"STORYBLOK_",
|
|
111
|
+
"CLOUDINARY_",
|
|
112
|
+
"IMGIX_",
|
|
113
|
+
"UPLOADTHING_",
|
|
114
|
+
"OPENAI_",
|
|
115
|
+
"ANTHROPIC_",
|
|
116
|
+
"REPLICATE_",
|
|
117
|
+
"HUGGINGFACE_",
|
|
118
|
+
"PLANETSCALE_",
|
|
119
|
+
"TURSO_",
|
|
120
|
+
"NEON_",
|
|
121
|
+
"UPSTASH_",
|
|
122
|
+
"PUSHER_",
|
|
123
|
+
"ABLY_",
|
|
124
|
+
"LIVEBLOCKS_",
|
|
125
|
+
"CONVEX_",
|
|
126
|
+
"COHERE_",
|
|
127
|
+
// Auth
|
|
128
|
+
"AUTH_",
|
|
129
|
+
"OAUTH_",
|
|
130
|
+
"GITHUB_CLIENT_",
|
|
131
|
+
"GOOGLE_CLIENT_",
|
|
132
|
+
// Generic infrastructure
|
|
133
|
+
"LOG_",
|
|
134
|
+
"CACHE_",
|
|
135
|
+
"QUEUE_",
|
|
136
|
+
"WORKER_",
|
|
137
|
+
"CRON_",
|
|
138
|
+
"WEBHOOK_",
|
|
139
|
+
"SMTP_",
|
|
140
|
+
"MAIL_",
|
|
141
|
+
"EMAIL_",
|
|
142
|
+
"S3_",
|
|
143
|
+
"CDN_",
|
|
144
|
+
"STORAGE_",
|
|
145
|
+
// App / project-specific
|
|
146
|
+
"VIBECHECK_"
|
|
147
|
+
];
|
|
148
|
+
var WELL_KNOWN_EXACT = /* @__PURE__ */ new Set([
|
|
149
|
+
// Node.js / OS
|
|
150
|
+
"NODE_ENV",
|
|
151
|
+
"CI",
|
|
152
|
+
"HOME",
|
|
153
|
+
"PATH",
|
|
154
|
+
"PWD",
|
|
155
|
+
"USER",
|
|
156
|
+
"SHELL",
|
|
157
|
+
"TERM",
|
|
158
|
+
"LANG",
|
|
159
|
+
"HOSTNAME",
|
|
160
|
+
"PORT",
|
|
161
|
+
"TZ",
|
|
162
|
+
"EDITOR",
|
|
163
|
+
"TMPDIR",
|
|
164
|
+
"TEMP",
|
|
165
|
+
"TMP",
|
|
166
|
+
"__dirname",
|
|
167
|
+
"__filename",
|
|
168
|
+
// Common app config
|
|
169
|
+
"DATABASE_URL",
|
|
170
|
+
"REDIS_URL",
|
|
171
|
+
"API_URL",
|
|
172
|
+
"BASE_URL",
|
|
173
|
+
"HOST",
|
|
174
|
+
"SECRET",
|
|
175
|
+
"JWT_SECRET",
|
|
176
|
+
"SESSION_SECRET",
|
|
177
|
+
"APP_URL",
|
|
178
|
+
"SITE_URL",
|
|
179
|
+
"PUBLIC_URL",
|
|
180
|
+
"APP_SECRET",
|
|
181
|
+
"APP_KEY",
|
|
182
|
+
"APP_NAME",
|
|
183
|
+
"APP_ENV",
|
|
184
|
+
"APP_DEBUG",
|
|
185
|
+
"DIRECT_URL",
|
|
186
|
+
"SHADOW_DATABASE_URL",
|
|
187
|
+
// Postgres individual vars
|
|
188
|
+
"PGHOST",
|
|
189
|
+
"PGPORT",
|
|
190
|
+
"PGUSER",
|
|
191
|
+
"PGPASSWORD",
|
|
192
|
+
"PGDATABASE",
|
|
193
|
+
// Vercel
|
|
194
|
+
"VERCEL",
|
|
195
|
+
"VERCEL_URL",
|
|
196
|
+
"VERCEL_ENV",
|
|
197
|
+
// Misc hosting
|
|
198
|
+
"HEROKU",
|
|
199
|
+
"RENDER",
|
|
200
|
+
"RAILWAY",
|
|
201
|
+
// CI
|
|
202
|
+
"GITHUB_ACTIONS",
|
|
203
|
+
"GITLAB_CI",
|
|
204
|
+
// Build
|
|
205
|
+
"ANALYZE",
|
|
206
|
+
"DEBUG",
|
|
207
|
+
"VERBOSE",
|
|
208
|
+
"VERSION",
|
|
209
|
+
// Common app config (extended)
|
|
210
|
+
"NEXTAUTH_URL",
|
|
211
|
+
"NEXTAUTH_SECRET",
|
|
212
|
+
"AUTH_SECRET",
|
|
213
|
+
"AUTH_URL",
|
|
214
|
+
"NEXT_PUBLIC_SUPABASE_URL",
|
|
215
|
+
"NEXT_PUBLIC_SUPABASE_ANON_KEY",
|
|
216
|
+
"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
|
|
217
|
+
"CLERK_SECRET_KEY",
|
|
218
|
+
"NEXT_PUBLIC_POSTHOG_KEY",
|
|
219
|
+
"NEXT_PUBLIC_POSTHOG_HOST",
|
|
220
|
+
"UPSTASH_REDIS_REST_URL",
|
|
221
|
+
"UPSTASH_REDIS_REST_TOKEN",
|
|
222
|
+
"TURSO_DATABASE_URL",
|
|
223
|
+
"TURSO_AUTH_TOKEN",
|
|
224
|
+
"KV_URL",
|
|
225
|
+
"KV_REST_API_URL",
|
|
226
|
+
"KV_REST_API_TOKEN",
|
|
227
|
+
"BLOB_READ_WRITE_TOKEN",
|
|
228
|
+
"EDGE_CONFIG",
|
|
229
|
+
"CRON_SECRET",
|
|
230
|
+
"WEBHOOK_SECRET"
|
|
231
|
+
]);
|
|
232
|
+
function isWellKnownEnvVar(name) {
|
|
233
|
+
if (WELL_KNOWN_EXACT.has(name)) return true;
|
|
234
|
+
return WELL_KNOWN_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
235
|
+
}
|
|
236
|
+
function levenshtein(a, b) {
|
|
237
|
+
const m = a.length, n = b.length;
|
|
238
|
+
const dp = Array.from(
|
|
239
|
+
{ length: m + 1 },
|
|
240
|
+
(_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
241
|
+
);
|
|
242
|
+
for (let i = 1; i <= m; i++) {
|
|
243
|
+
for (let j = 1; j <= n; j++) {
|
|
244
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return dp[m][n];
|
|
248
|
+
}
|
|
249
|
+
var EnvVarEngine = class {
|
|
250
|
+
constructor(_envIndex, optsOrThreshold = {}) {
|
|
251
|
+
this._envIndex = _envIndex;
|
|
252
|
+
this._resolvedOpts = typeof optsOrThreshold === "number" ? { confidenceThreshold: optsOrThreshold } : optsOrThreshold;
|
|
253
|
+
}
|
|
254
|
+
id = "env_var";
|
|
255
|
+
_resolvedOpts;
|
|
256
|
+
get _confidenceThreshold() {
|
|
257
|
+
return this._resolvedOpts.confidenceThreshold ?? 0.75;
|
|
258
|
+
}
|
|
259
|
+
get _exampleOnly() {
|
|
260
|
+
return this._resolvedOpts.exampleOnly ?? false;
|
|
261
|
+
}
|
|
262
|
+
get _noEnvFiles() {
|
|
263
|
+
return this._resolvedOpts.noEnvFiles ?? false;
|
|
264
|
+
}
|
|
265
|
+
async scan(delta, signal) {
|
|
266
|
+
if (signal?.aborted) return [];
|
|
267
|
+
if (this._noEnvFiles) return [];
|
|
268
|
+
if (this._envIndex.index.size === 0) return [];
|
|
269
|
+
const source = delta.fullText;
|
|
270
|
+
if (!source.includes("process.env") && !source.includes("import.meta.env")) return [];
|
|
271
|
+
try {
|
|
272
|
+
const findings = [];
|
|
273
|
+
const lines = delta.lines ?? source.split("\n");
|
|
274
|
+
let inBlockComment = false;
|
|
275
|
+
const seen = /* @__PURE__ */ new Set();
|
|
276
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
277
|
+
if (signal?.aborted) break;
|
|
278
|
+
const line = lines[lineNum];
|
|
279
|
+
if (inBlockComment) {
|
|
280
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const trimmed = line.trimStart();
|
|
284
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#")) continue;
|
|
285
|
+
if (trimmed.startsWith("/*")) {
|
|
286
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (trimmed.startsWith("*")) continue;
|
|
290
|
+
if (/^\s*(?:type\s|interface\s|declare\s)/.test(line)) continue;
|
|
291
|
+
DESTRUCTURE_PATTERN.lastIndex = 0;
|
|
292
|
+
let dm;
|
|
293
|
+
while ((dm = DESTRUCTURE_PATTERN.exec(line)) !== null) {
|
|
294
|
+
const vars = dm[1].split(",").map((v) => {
|
|
295
|
+
const trimmedVar = v.trim();
|
|
296
|
+
const colonIdx = trimmedVar.indexOf(":");
|
|
297
|
+
const eqIdx = trimmedVar.indexOf("=");
|
|
298
|
+
const hasDefault = eqIdx !== -1;
|
|
299
|
+
let name = trimmedVar;
|
|
300
|
+
if (colonIdx !== -1 && (eqIdx === -1 || colonIdx < eqIdx)) {
|
|
301
|
+
name = trimmedVar.slice(0, colonIdx).trim();
|
|
302
|
+
} else if (eqIdx !== -1) {
|
|
303
|
+
name = trimmedVar.slice(0, eqIdx).trim();
|
|
304
|
+
}
|
|
305
|
+
return { name, hasDefault };
|
|
306
|
+
});
|
|
307
|
+
for (const { name, hasDefault } of vars) {
|
|
308
|
+
if (!name || !/^[A-Z_][A-Z0-9_]*$/.test(name)) continue;
|
|
309
|
+
if (this._envIndex.has(name)) continue;
|
|
310
|
+
if (isWellKnownEnvVar(name)) continue;
|
|
311
|
+
if (hasDefault) continue;
|
|
312
|
+
const dedupeKey = `${lineNum}:${name}`;
|
|
313
|
+
if (seen.has(dedupeKey)) continue;
|
|
314
|
+
seen.add(dedupeKey);
|
|
315
|
+
const suggestion = this._findSimilar(name);
|
|
316
|
+
const confidence = suggestion ? 0.9 : 0.85;
|
|
317
|
+
if (confidence < this._confidenceThreshold) continue;
|
|
318
|
+
const severity = this._exampleOnly ? "info" : suggestion ? "high" : "info";
|
|
319
|
+
findings.push({
|
|
320
|
+
id: deterministicId(delta.documentUri, lineNum + 1, name),
|
|
321
|
+
engine: "env_var",
|
|
322
|
+
severity,
|
|
323
|
+
category: "env_var",
|
|
324
|
+
file: delta.documentUri,
|
|
325
|
+
line: lineNum + 1,
|
|
326
|
+
column: dm.index,
|
|
327
|
+
endLine: lineNum + 1,
|
|
328
|
+
endColumn: dm.index + dm[0].length,
|
|
329
|
+
message: this._exampleOnly ? `\`${name}\` (destructured from process.env) is not documented in .env.example` : `\`${name}\` (destructured from process.env) is not defined in .env or CI config`,
|
|
330
|
+
evidence: dm[0],
|
|
331
|
+
suggestion: suggestion ? `Typo? Similar: ${suggestion}` : `Add ${name} to your .env file`,
|
|
332
|
+
confidence,
|
|
333
|
+
autoFixable: false,
|
|
334
|
+
ruleId: "ENV001"
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
BRACKET_PATTERN.lastIndex = 0;
|
|
339
|
+
let bm;
|
|
340
|
+
while ((bm = BRACKET_PATTERN.exec(line)) !== null) {
|
|
341
|
+
const varName = bm[1];
|
|
342
|
+
if (this._envIndex.has(varName)) continue;
|
|
343
|
+
const suggestion = this._findSimilar(varName);
|
|
344
|
+
if (isWellKnownEnvVar(varName) && !suggestion) continue;
|
|
345
|
+
const matchEnd = bm.index + bm[0].length;
|
|
346
|
+
if (hasFallback(line, bm.index, matchEnd)) continue;
|
|
347
|
+
const dedupeKey = `${lineNum}:${varName}`;
|
|
348
|
+
if (seen.has(dedupeKey)) continue;
|
|
349
|
+
seen.add(dedupeKey);
|
|
350
|
+
const confidence = suggestion ? 0.9 : 0.85;
|
|
351
|
+
if (confidence < this._confidenceThreshold) continue;
|
|
352
|
+
const severity = this._exampleOnly ? "info" : suggestion ? "high" : "info";
|
|
353
|
+
findings.push({
|
|
354
|
+
id: deterministicId(delta.documentUri, lineNum + 1, varName),
|
|
355
|
+
engine: "env_var",
|
|
356
|
+
severity,
|
|
357
|
+
category: "env_var",
|
|
358
|
+
file: delta.documentUri,
|
|
359
|
+
line: lineNum + 1,
|
|
360
|
+
column: bm.index,
|
|
361
|
+
endLine: lineNum + 1,
|
|
362
|
+
endColumn: matchEnd,
|
|
363
|
+
message: this._exampleOnly ? `\`${varName}\` is not documented in .env.example` : `\`${varName}\` is not defined in .env or CI config`,
|
|
364
|
+
evidence: bm[0],
|
|
365
|
+
suggestion: suggestion ? `Typo? Similar: ${suggestion}` : `Add ${varName} to your .env file`,
|
|
366
|
+
confidence,
|
|
367
|
+
autoFixable: false,
|
|
368
|
+
ruleId: "ENV001"
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
IMPORT_META_ENV_PATTERN.lastIndex = 0;
|
|
372
|
+
let iem;
|
|
373
|
+
while ((iem = IMPORT_META_ENV_PATTERN.exec(line)) !== null) {
|
|
374
|
+
const varName = iem[1];
|
|
375
|
+
if (this._envIndex.has(varName)) continue;
|
|
376
|
+
const suggestion = this._findSimilar(varName);
|
|
377
|
+
if (isWellKnownEnvVar(varName) && !suggestion) continue;
|
|
378
|
+
const matchEnd = iem.index + iem[0].length;
|
|
379
|
+
if (hasFallback(line, iem.index, matchEnd)) continue;
|
|
380
|
+
const dedupeKey = `${lineNum}:${varName}`;
|
|
381
|
+
if (seen.has(dedupeKey)) continue;
|
|
382
|
+
seen.add(dedupeKey);
|
|
383
|
+
const confidence = suggestion ? 0.9 : 0.85;
|
|
384
|
+
if (confidence < this._confidenceThreshold) continue;
|
|
385
|
+
const severity = this._exampleOnly ? "info" : suggestion ? "high" : "info";
|
|
386
|
+
findings.push({
|
|
387
|
+
id: deterministicId(delta.documentUri, lineNum + 1, varName),
|
|
388
|
+
engine: "env_var",
|
|
389
|
+
severity,
|
|
390
|
+
category: "env_var",
|
|
391
|
+
file: delta.documentUri,
|
|
392
|
+
line: lineNum + 1,
|
|
393
|
+
column: iem.index,
|
|
394
|
+
endLine: lineNum + 1,
|
|
395
|
+
endColumn: matchEnd,
|
|
396
|
+
message: `\`${varName}\` is not defined in .env or CI config`,
|
|
397
|
+
evidence: `import.meta.env.${varName}`,
|
|
398
|
+
suggestion: suggestion ? `Typo? Similar: ${suggestion}` : `Add ${varName} to your .env file`,
|
|
399
|
+
confidence,
|
|
400
|
+
autoFixable: false,
|
|
401
|
+
ruleId: "ENV001"
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
ENV_PATTERN.lastIndex = 0;
|
|
405
|
+
let m;
|
|
406
|
+
while ((m = ENV_PATTERN.exec(line)) !== null) {
|
|
407
|
+
const varName = m[1];
|
|
408
|
+
if (this._envIndex.has(varName)) continue;
|
|
409
|
+
const suggestion = this._findSimilar(varName);
|
|
410
|
+
if (isWellKnownEnvVar(varName) && !suggestion) continue;
|
|
411
|
+
const matchEnd = m.index + m[0].length;
|
|
412
|
+
if (hasFallback(line, m.index, matchEnd)) continue;
|
|
413
|
+
const dedupeKey = `${lineNum}:${varName}`;
|
|
414
|
+
if (seen.has(dedupeKey)) continue;
|
|
415
|
+
seen.add(dedupeKey);
|
|
416
|
+
const column = m.index;
|
|
417
|
+
const confidence = suggestion ? 0.9 : 0.85;
|
|
418
|
+
if (confidence < this._confidenceThreshold) continue;
|
|
419
|
+
const isToggle = isConditionalCheck(line, m.index);
|
|
420
|
+
let severity;
|
|
421
|
+
if (this._exampleOnly) {
|
|
422
|
+
severity = "info";
|
|
423
|
+
} else if (suggestion) {
|
|
424
|
+
severity = "high";
|
|
425
|
+
} else if (isToggle) {
|
|
426
|
+
severity = "info";
|
|
427
|
+
} else {
|
|
428
|
+
severity = "info";
|
|
429
|
+
}
|
|
430
|
+
findings.push({
|
|
431
|
+
id: deterministicId(delta.documentUri, lineNum + 1, varName),
|
|
432
|
+
engine: "env_var",
|
|
433
|
+
severity,
|
|
434
|
+
category: "env_var",
|
|
435
|
+
file: delta.documentUri,
|
|
436
|
+
line: lineNum + 1,
|
|
437
|
+
column,
|
|
438
|
+
endLine: lineNum + 1,
|
|
439
|
+
endColumn: column + `process.env.${varName}`.length,
|
|
440
|
+
message: this._exampleOnly ? `\`${varName}\` is not documented in .env.example (informational)` : `\`${varName}\` is not defined in .env or CI config`,
|
|
441
|
+
evidence: `process.env.${varName}`,
|
|
442
|
+
suggestion: suggestion ? `Typo? Similar: ${suggestion}` : `Add ${varName} to your .env file`,
|
|
443
|
+
confidence,
|
|
444
|
+
autoFixable: false,
|
|
445
|
+
ruleId: "ENV001"
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return findings;
|
|
450
|
+
} catch (error) {
|
|
451
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
452
|
+
console.error(`[EnvVarEngine] Scan failed for ${delta.documentUri}:`, errorMsg);
|
|
453
|
+
return [{
|
|
454
|
+
id: deterministicId(delta.documentUri, 1, "ENGINE_ERROR"),
|
|
455
|
+
engine: "env_var",
|
|
456
|
+
severity: "medium",
|
|
457
|
+
category: "engine_error",
|
|
458
|
+
file: delta.documentUri,
|
|
459
|
+
line: 1,
|
|
460
|
+
column: 0,
|
|
461
|
+
endLine: 1,
|
|
462
|
+
endColumn: 0,
|
|
463
|
+
message: `Environment Variable Engine scan failed: ${errorMsg}`,
|
|
464
|
+
evidence: "Engine internal error",
|
|
465
|
+
suggestion: "Check file syntax and report this issue if it persists",
|
|
466
|
+
confidence: 1,
|
|
467
|
+
autoFixable: false,
|
|
468
|
+
ruleId: "ENGINE_ERROR"
|
|
469
|
+
}];
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
_findSimilar(name) {
|
|
473
|
+
let best;
|
|
474
|
+
let bestDist = 3;
|
|
475
|
+
for (const v of this._envIndex.index) {
|
|
476
|
+
if (Math.abs(v.length - name.length) >= bestDist) continue;
|
|
477
|
+
const d = levenshtein(name, v);
|
|
478
|
+
if (d <= 2 && d < bestDist) {
|
|
479
|
+
bestDist = d;
|
|
480
|
+
best = v;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!best) {
|
|
484
|
+
const parts = name.split("_");
|
|
485
|
+
if (parts.length >= 3) {
|
|
486
|
+
const prefix = parts.slice(0, -1).join("_") + "_";
|
|
487
|
+
for (const v of this._envIndex.index) {
|
|
488
|
+
if (v.startsWith(prefix) && v !== name) {
|
|
489
|
+
best = v;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return best;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
export { EnvVarEngine };
|