@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,1968 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { dirname, join, resolve } from 'path';
|
|
5
|
+
import { isRelativeOrAlias, extractPackageName, extractJavaScriptNpmImports } from './chunk-NR36RTVO.js';
|
|
6
|
+
import { readFileSync, existsSync, readdirSync, unwatchFile, watchFile, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
|
|
9
|
+
createRequire(import.meta.url);
|
|
10
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
11
|
+
dirname(__filename$1);
|
|
12
|
+
var EngineErrorCode = /* @__PURE__ */ ((EngineErrorCode2) => {
|
|
13
|
+
EngineErrorCode2["INVALID_CONFIG"] = "INVALID_CONFIG";
|
|
14
|
+
EngineErrorCode2["MISSING_CONFIG"] = "MISSING_CONFIG";
|
|
15
|
+
EngineErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
16
|
+
EngineErrorCode2["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
17
|
+
EngineErrorCode2["IO_ERROR"] = "IO_ERROR";
|
|
18
|
+
EngineErrorCode2["PARSE_ERROR"] = "PARSE_ERROR";
|
|
19
|
+
EngineErrorCode2["INVALID_SYNTAX"] = "INVALID_SYNTAX";
|
|
20
|
+
EngineErrorCode2["CACHE_ERROR"] = "CACHE_ERROR";
|
|
21
|
+
EngineErrorCode2["CACHE_MISS"] = "CACHE_MISS";
|
|
22
|
+
EngineErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
23
|
+
EngineErrorCode2["INVALID_INPUT"] = "INVALID_INPUT";
|
|
24
|
+
EngineErrorCode2["RUNTIME_ERROR"] = "RUNTIME_ERROR";
|
|
25
|
+
EngineErrorCode2["MEMORY_ERROR"] = "MEMORY_ERROR";
|
|
26
|
+
EngineErrorCode2["RESOURCE_EXHAUSTED"] = "RESOURCE_EXHAUSTED";
|
|
27
|
+
EngineErrorCode2["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
28
|
+
EngineErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
29
|
+
EngineErrorCode2["AUTHENTICATION_ERROR"] = "AUTHENTICATION_ERROR";
|
|
30
|
+
EngineErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
31
|
+
EngineErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
32
|
+
return EngineErrorCode2;
|
|
33
|
+
})(EngineErrorCode || {});
|
|
34
|
+
var EngineError = class _EngineError extends Error {
|
|
35
|
+
code;
|
|
36
|
+
context;
|
|
37
|
+
recoverable;
|
|
38
|
+
retryable;
|
|
39
|
+
constructor(code, message, context = {}, recoverable = false, retryable = false) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "EngineError";
|
|
42
|
+
this.code = code;
|
|
43
|
+
this.context = context;
|
|
44
|
+
this.recoverable = recoverable;
|
|
45
|
+
this.retryable = retryable;
|
|
46
|
+
if (Error.captureStackTrace) {
|
|
47
|
+
Error.captureStackTrace(this, _EngineError);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert to serializable format for logging/transport
|
|
52
|
+
*/
|
|
53
|
+
toJSON() {
|
|
54
|
+
return {
|
|
55
|
+
name: this.name,
|
|
56
|
+
code: this.code,
|
|
57
|
+
message: this.message,
|
|
58
|
+
context: this.context,
|
|
59
|
+
recoverable: this.recoverable,
|
|
60
|
+
retryable: this.retryable,
|
|
61
|
+
stack: this.stack
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a network error with retry capability
|
|
66
|
+
*/
|
|
67
|
+
static network(message, context = {}) {
|
|
68
|
+
return new _EngineError(
|
|
69
|
+
"NETWORK_ERROR",
|
|
70
|
+
message,
|
|
71
|
+
context,
|
|
72
|
+
true,
|
|
73
|
+
// recoverable
|
|
74
|
+
true
|
|
75
|
+
// retryable
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a timeout error with retry capability
|
|
80
|
+
*/
|
|
81
|
+
static timeout(message, context = {}) {
|
|
82
|
+
return new _EngineError(
|
|
83
|
+
"TIMEOUT_ERROR",
|
|
84
|
+
message,
|
|
85
|
+
context,
|
|
86
|
+
true,
|
|
87
|
+
// recoverable
|
|
88
|
+
true
|
|
89
|
+
// retryable
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create a parse error (not retryable)
|
|
94
|
+
*/
|
|
95
|
+
static parse(message, context = {}) {
|
|
96
|
+
return new _EngineError(
|
|
97
|
+
"PARSE_ERROR",
|
|
98
|
+
message,
|
|
99
|
+
context,
|
|
100
|
+
false,
|
|
101
|
+
// not recoverable
|
|
102
|
+
false
|
|
103
|
+
// not retryable
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a configuration error (not retryable)
|
|
108
|
+
*/
|
|
109
|
+
static config(message, context = {}) {
|
|
110
|
+
return new _EngineError(
|
|
111
|
+
"INVALID_CONFIG",
|
|
112
|
+
message,
|
|
113
|
+
context,
|
|
114
|
+
false,
|
|
115
|
+
// not recoverable
|
|
116
|
+
false
|
|
117
|
+
// not retryable
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a service unavailable error (retryable)
|
|
122
|
+
*/
|
|
123
|
+
static unavailable(message, context = {}) {
|
|
124
|
+
return new _EngineError(
|
|
125
|
+
"SERVICE_UNAVAILABLE",
|
|
126
|
+
message,
|
|
127
|
+
context,
|
|
128
|
+
true,
|
|
129
|
+
// recoverable
|
|
130
|
+
true
|
|
131
|
+
// retryable
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Create a validation error (not retryable)
|
|
136
|
+
*/
|
|
137
|
+
static validation(message, context = {}) {
|
|
138
|
+
return new _EngineError(
|
|
139
|
+
"VALIDATION_ERROR",
|
|
140
|
+
message,
|
|
141
|
+
context,
|
|
142
|
+
false,
|
|
143
|
+
// not recoverable
|
|
144
|
+
false
|
|
145
|
+
// not retryable
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function withEngineErrorHandling(operation, context = {}, errorMapper) {
|
|
150
|
+
return operation().catch((error) => {
|
|
151
|
+
if (error instanceof EngineError) {
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
if (error instanceof Error) {
|
|
155
|
+
if (error.name === "AbortError" || error.message.includes("timeout")) {
|
|
156
|
+
throw EngineError.timeout(error.message, context);
|
|
157
|
+
}
|
|
158
|
+
if (error.message.includes("fetch") || error.message.includes("network")) {
|
|
159
|
+
throw EngineError.network(error.message, context);
|
|
160
|
+
}
|
|
161
|
+
if (error.message.includes("parse") || error.message.includes("syntax")) {
|
|
162
|
+
throw EngineError.parse(error.message, context);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (errorMapper) {
|
|
166
|
+
throw errorMapper(error);
|
|
167
|
+
}
|
|
168
|
+
throw new EngineError(
|
|
169
|
+
"UNKNOWN_ERROR",
|
|
170
|
+
error instanceof Error ? error.message : String(error),
|
|
171
|
+
context,
|
|
172
|
+
false,
|
|
173
|
+
false
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function isEngineError(error) {
|
|
178
|
+
return error instanceof EngineError;
|
|
179
|
+
}
|
|
180
|
+
function createContext(engine, filePath, additional = {}) {
|
|
181
|
+
return {
|
|
182
|
+
engine,
|
|
183
|
+
filePath,
|
|
184
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
185
|
+
...additional
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
var javascriptLanguageProfile = {
|
|
189
|
+
id: "javascript",
|
|
190
|
+
extractImportSpecifiers: extractJavaScriptNpmImports
|
|
191
|
+
};
|
|
192
|
+
var PY_STDLIB_TOP = /* @__PURE__ */ new Set([
|
|
193
|
+
"abc",
|
|
194
|
+
"aifc",
|
|
195
|
+
"argparse",
|
|
196
|
+
"array",
|
|
197
|
+
"ast",
|
|
198
|
+
"asyncio",
|
|
199
|
+
"atexit",
|
|
200
|
+
"base64",
|
|
201
|
+
"bdb",
|
|
202
|
+
"binascii",
|
|
203
|
+
"bisect",
|
|
204
|
+
"builtins",
|
|
205
|
+
"bz2",
|
|
206
|
+
"calendar",
|
|
207
|
+
"cgi",
|
|
208
|
+
"cgitb",
|
|
209
|
+
"chunk",
|
|
210
|
+
"cmath",
|
|
211
|
+
"cmd",
|
|
212
|
+
"code",
|
|
213
|
+
"codecs",
|
|
214
|
+
"codeop",
|
|
215
|
+
"collections",
|
|
216
|
+
"colorsys",
|
|
217
|
+
"compileall",
|
|
218
|
+
"concurrent",
|
|
219
|
+
"configparser",
|
|
220
|
+
"contextlib",
|
|
221
|
+
"contextvars",
|
|
222
|
+
"copy",
|
|
223
|
+
"copyreg",
|
|
224
|
+
"cProfile",
|
|
225
|
+
"crypt",
|
|
226
|
+
"csv",
|
|
227
|
+
"ctypes",
|
|
228
|
+
"curses",
|
|
229
|
+
"dataclasses",
|
|
230
|
+
"datetime",
|
|
231
|
+
"dbm",
|
|
232
|
+
"decimal",
|
|
233
|
+
"difflib",
|
|
234
|
+
"dis",
|
|
235
|
+
"distutils",
|
|
236
|
+
"doctest",
|
|
237
|
+
"email",
|
|
238
|
+
"encodings",
|
|
239
|
+
"enum",
|
|
240
|
+
"errno",
|
|
241
|
+
"faulthandler",
|
|
242
|
+
"fcntl",
|
|
243
|
+
"filecmp",
|
|
244
|
+
"fileinput",
|
|
245
|
+
"fnmatch",
|
|
246
|
+
"fractions",
|
|
247
|
+
"ftplib",
|
|
248
|
+
"functools",
|
|
249
|
+
"gc",
|
|
250
|
+
"getopt",
|
|
251
|
+
"getpass",
|
|
252
|
+
"gettext",
|
|
253
|
+
"glob",
|
|
254
|
+
"graphlib",
|
|
255
|
+
"grp",
|
|
256
|
+
"gzip",
|
|
257
|
+
"hashlib",
|
|
258
|
+
"heapq",
|
|
259
|
+
"hmac",
|
|
260
|
+
"html",
|
|
261
|
+
"http",
|
|
262
|
+
"idlelib",
|
|
263
|
+
"imaplib",
|
|
264
|
+
"imghdr",
|
|
265
|
+
"imp",
|
|
266
|
+
"importlib",
|
|
267
|
+
"inspect",
|
|
268
|
+
"io",
|
|
269
|
+
"ipaddress",
|
|
270
|
+
"itertools",
|
|
271
|
+
"json",
|
|
272
|
+
"keyword",
|
|
273
|
+
"lib2to3",
|
|
274
|
+
"linecache",
|
|
275
|
+
"locale",
|
|
276
|
+
"logging",
|
|
277
|
+
"lzma",
|
|
278
|
+
"mailbox",
|
|
279
|
+
"mailcap",
|
|
280
|
+
"marshal",
|
|
281
|
+
"math",
|
|
282
|
+
"mimetypes",
|
|
283
|
+
"mmap",
|
|
284
|
+
"modulefinder",
|
|
285
|
+
"multiprocessing",
|
|
286
|
+
"netrc",
|
|
287
|
+
"nis",
|
|
288
|
+
"nntplib",
|
|
289
|
+
"numbers",
|
|
290
|
+
"operator",
|
|
291
|
+
"optparse",
|
|
292
|
+
"os",
|
|
293
|
+
"ossaudiodev",
|
|
294
|
+
"pathlib",
|
|
295
|
+
"pdb",
|
|
296
|
+
"pickle",
|
|
297
|
+
"pickletools",
|
|
298
|
+
"pipes",
|
|
299
|
+
"pkgutil",
|
|
300
|
+
"platform",
|
|
301
|
+
"plistlib",
|
|
302
|
+
"poplib",
|
|
303
|
+
"posix",
|
|
304
|
+
"posixpath",
|
|
305
|
+
"pprint",
|
|
306
|
+
"profile",
|
|
307
|
+
"pstats",
|
|
308
|
+
"pty",
|
|
309
|
+
"pwd",
|
|
310
|
+
"py_compile",
|
|
311
|
+
"pyclbr",
|
|
312
|
+
"pydoc",
|
|
313
|
+
"queue",
|
|
314
|
+
"quopri",
|
|
315
|
+
"random",
|
|
316
|
+
"re",
|
|
317
|
+
"readline",
|
|
318
|
+
"reprlib",
|
|
319
|
+
"resource",
|
|
320
|
+
"rlcompleter",
|
|
321
|
+
"runpy",
|
|
322
|
+
"sched",
|
|
323
|
+
"secrets",
|
|
324
|
+
"select",
|
|
325
|
+
"selectors",
|
|
326
|
+
"shelve",
|
|
327
|
+
"shlex",
|
|
328
|
+
"shutil",
|
|
329
|
+
"signal",
|
|
330
|
+
"site",
|
|
331
|
+
"smtpd",
|
|
332
|
+
"smtplib",
|
|
333
|
+
"sndhdr",
|
|
334
|
+
"socket",
|
|
335
|
+
"socketserver",
|
|
336
|
+
"sqlite3",
|
|
337
|
+
"ssl",
|
|
338
|
+
"stat",
|
|
339
|
+
"statistics",
|
|
340
|
+
"string",
|
|
341
|
+
"stringprep",
|
|
342
|
+
"struct",
|
|
343
|
+
"subprocess",
|
|
344
|
+
"sunau",
|
|
345
|
+
"symtable",
|
|
346
|
+
"sys",
|
|
347
|
+
"sysconfig",
|
|
348
|
+
"syslog",
|
|
349
|
+
"tabnanny",
|
|
350
|
+
"tarfile",
|
|
351
|
+
"telnetlib",
|
|
352
|
+
"tempfile",
|
|
353
|
+
"termios",
|
|
354
|
+
"test",
|
|
355
|
+
"textwrap",
|
|
356
|
+
"threading",
|
|
357
|
+
"time",
|
|
358
|
+
"timeit",
|
|
359
|
+
"tkinter",
|
|
360
|
+
"token",
|
|
361
|
+
"tokenize",
|
|
362
|
+
"trace",
|
|
363
|
+
"traceback",
|
|
364
|
+
"tracemalloc",
|
|
365
|
+
"tty",
|
|
366
|
+
"turtle",
|
|
367
|
+
"turtledemo",
|
|
368
|
+
"types",
|
|
369
|
+
"typing",
|
|
370
|
+
"unicodedata",
|
|
371
|
+
"unittest",
|
|
372
|
+
"urllib",
|
|
373
|
+
"uu",
|
|
374
|
+
"uuid",
|
|
375
|
+
"venv",
|
|
376
|
+
"warnings",
|
|
377
|
+
"wave",
|
|
378
|
+
"weakref",
|
|
379
|
+
"webbrowser",
|
|
380
|
+
"winsound",
|
|
381
|
+
"wsgiref",
|
|
382
|
+
"xdrlib",
|
|
383
|
+
"xml",
|
|
384
|
+
"xmlrpc",
|
|
385
|
+
"zipapp",
|
|
386
|
+
"zipfile",
|
|
387
|
+
"zipimport",
|
|
388
|
+
"zlib",
|
|
389
|
+
"__future__",
|
|
390
|
+
"typing_extensions"
|
|
391
|
+
]);
|
|
392
|
+
function shouldSkipTop(name) {
|
|
393
|
+
const top = name.split(".")[0];
|
|
394
|
+
if (!top || top.startsWith("_")) return true;
|
|
395
|
+
return PY_STDLIB_TOP.has(top);
|
|
396
|
+
}
|
|
397
|
+
function pushUnique(out, seen, packageName, line, column, code, importKind) {
|
|
398
|
+
if (shouldSkipTop(packageName)) return;
|
|
399
|
+
const top = packageName.split(".")[0];
|
|
400
|
+
if (seen.has(top)) return;
|
|
401
|
+
seen.add(top);
|
|
402
|
+
out.push({
|
|
403
|
+
specifier: packageName,
|
|
404
|
+
packageName: top,
|
|
405
|
+
subpath: packageName.includes(".") ? packageName.slice(top.length + 1) : void 0,
|
|
406
|
+
line,
|
|
407
|
+
column,
|
|
408
|
+
code,
|
|
409
|
+
importKind
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function extractPythonPypiImports(source, preLines) {
|
|
413
|
+
const results = [];
|
|
414
|
+
const seen = /* @__PURE__ */ new Set();
|
|
415
|
+
const lines = preLines ?? source.split("\n");
|
|
416
|
+
const reImportFrom = /^\s*from\s+([\w.]+)\s+import\s+/;
|
|
417
|
+
const reImportPlain = /^\s*import\s+([\w.]+(?:\s*,\s*[\w.]+)*)\s*(?:#|$)/;
|
|
418
|
+
const reImportAs = /^\s*import\s+([\w.]+)\s+as\s+\w+/;
|
|
419
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
420
|
+
const line = lines[lineIdx];
|
|
421
|
+
const trimmed = line.trimStart();
|
|
422
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
423
|
+
const mFrom = trimmed.match(reImportFrom);
|
|
424
|
+
if (mFrom) {
|
|
425
|
+
const mod = mFrom[1].trim();
|
|
426
|
+
if (mod === "__future__") continue;
|
|
427
|
+
pushUnique(results, seen, mod, lineIdx + 1, line.indexOf(mod), trimmed, "value");
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const mAs = trimmed.match(reImportAs);
|
|
431
|
+
if (mAs) {
|
|
432
|
+
pushUnique(results, seen, mAs[1].trim(), lineIdx + 1, line.indexOf(mAs[1]), trimmed, "value");
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const mPlain = trimmed.match(reImportPlain);
|
|
436
|
+
if (mPlain) {
|
|
437
|
+
const parts = mPlain[1].split(/\s*,\s*/);
|
|
438
|
+
for (const p of parts) {
|
|
439
|
+
const mod = p.trim();
|
|
440
|
+
if (!mod || mod === "__future__") continue;
|
|
441
|
+
pushUnique(results, seen, mod, lineIdx + 1, line.indexOf(mod), trimmed, "value");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return results;
|
|
446
|
+
}
|
|
447
|
+
var pythonLanguageProfile = {
|
|
448
|
+
id: "python",
|
|
449
|
+
extractImportSpecifiers: extractPythonPypiImports
|
|
450
|
+
};
|
|
451
|
+
var goLanguageProfile = {
|
|
452
|
+
id: "go",
|
|
453
|
+
extractImportSpecifiers(_source, _preLines) {
|
|
454
|
+
return [];
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
var JS_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
458
|
+
".ts",
|
|
459
|
+
".tsx",
|
|
460
|
+
".js",
|
|
461
|
+
".jsx",
|
|
462
|
+
".mjs",
|
|
463
|
+
".cjs",
|
|
464
|
+
".mts",
|
|
465
|
+
".cts"
|
|
466
|
+
]);
|
|
467
|
+
function getLanguageProfile(delta) {
|
|
468
|
+
const lang = delta.documentLanguage.toLowerCase();
|
|
469
|
+
const uriPath = delta.documentUri.replace(/^file:\/\//, "");
|
|
470
|
+
const ext = path.extname(uriPath).toLowerCase();
|
|
471
|
+
const base = path.basename(uriPath).toLowerCase();
|
|
472
|
+
if (lang.includes("python") || ext === ".py" || ext === ".pyi") return pythonLanguageProfile;
|
|
473
|
+
if (lang.includes("go") || ext === ".go" || base === "go.mod" || base === "go.work") return goLanguageProfile;
|
|
474
|
+
const isJsLang = lang.includes("typescript") || lang.includes("javascript") || lang.includes("typescriptreact") || lang.includes("javascriptreact");
|
|
475
|
+
const isJsExt = JS_EXTENSIONS.has(ext);
|
|
476
|
+
if (isJsLang || isJsExt) return javascriptLanguageProfile;
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
function deterministicId(uri, line, col, category, pkg) {
|
|
480
|
+
const input = `phantom:${uri}::${line}::${col}::${category}::${pkg}`;
|
|
481
|
+
let hash = 2166136261;
|
|
482
|
+
for (let i = 0; i < input.length; i++) {
|
|
483
|
+
hash ^= input.charCodeAt(i);
|
|
484
|
+
hash = hash * 16777619 >>> 0;
|
|
485
|
+
}
|
|
486
|
+
return `phantom-${hash.toString(16).padStart(8, "0")}`;
|
|
487
|
+
}
|
|
488
|
+
var KNOWN_FAKE_PACKAGES = /* @__PURE__ */ new Set([
|
|
489
|
+
// Auth
|
|
490
|
+
"react-auth-provider",
|
|
491
|
+
"react-secure-auth",
|
|
492
|
+
"next-auth-helpers",
|
|
493
|
+
"react-auth-kit-v2",
|
|
494
|
+
"auth-provider-react",
|
|
495
|
+
"next-auth-provider",
|
|
496
|
+
"next-auth-client",
|
|
497
|
+
"vercel-auth",
|
|
498
|
+
"vercel-auth-helpers",
|
|
499
|
+
"supabase-auth-helpers",
|
|
500
|
+
"supabase-auth-utils",
|
|
501
|
+
"firebase-auth-helpers",
|
|
502
|
+
"clerk-auth-utils",
|
|
503
|
+
"auth0-helpers",
|
|
504
|
+
"passport-auth-helpers",
|
|
505
|
+
// Payments
|
|
506
|
+
"stripe-auth-helper",
|
|
507
|
+
"stripe-payment-helper",
|
|
508
|
+
"stripe-checkout-helper",
|
|
509
|
+
"stripe-webhook-utils",
|
|
510
|
+
"stripe-utils",
|
|
511
|
+
"paypal-checkout-helper",
|
|
512
|
+
// Utilities
|
|
513
|
+
"string-utils-pro",
|
|
514
|
+
"array-helpers-ts",
|
|
515
|
+
"object-deep-merge",
|
|
516
|
+
"config-loader-pro",
|
|
517
|
+
"api-response-handler",
|
|
518
|
+
"env-loader-utils",
|
|
519
|
+
"lodash-helpers",
|
|
520
|
+
"ramda-utils",
|
|
521
|
+
"underscore-helpers",
|
|
522
|
+
// AI / ML
|
|
523
|
+
"openai-helpers",
|
|
524
|
+
"openai-utils",
|
|
525
|
+
"anthropic-helpers",
|
|
526
|
+
"claude-helpers",
|
|
527
|
+
"langchain-helpers",
|
|
528
|
+
"langchain-utils",
|
|
529
|
+
"ai-sdk-helpers",
|
|
530
|
+
// Next.js
|
|
531
|
+
"next-seo-helper",
|
|
532
|
+
"next-middleware-helper",
|
|
533
|
+
"next-api-handler",
|
|
534
|
+
"next-server-utils",
|
|
535
|
+
"next-router-helpers",
|
|
536
|
+
"next-image-utils",
|
|
537
|
+
// React ecosystem
|
|
538
|
+
"react-query-helpers",
|
|
539
|
+
"react-store-helpers",
|
|
540
|
+
"react-form-validator",
|
|
541
|
+
"react-hook-form-validator",
|
|
542
|
+
"react-state-manager",
|
|
543
|
+
"react-context-utils",
|
|
544
|
+
"react-router-helpers",
|
|
545
|
+
"react-animation-utils",
|
|
546
|
+
"react-modal-helper",
|
|
547
|
+
// Database / ORM
|
|
548
|
+
"prisma-helpers",
|
|
549
|
+
"prisma-utils",
|
|
550
|
+
"drizzle-helpers",
|
|
551
|
+
"drizzle-utils",
|
|
552
|
+
"mongoose-helpers",
|
|
553
|
+
"sequelize-helpers",
|
|
554
|
+
"typeorm-utils",
|
|
555
|
+
// Server
|
|
556
|
+
"express-middleware-helper",
|
|
557
|
+
"express-validator-pro",
|
|
558
|
+
"fastify-helpers",
|
|
559
|
+
"koa-helpers",
|
|
560
|
+
"hono-utils",
|
|
561
|
+
// Misc
|
|
562
|
+
"jwt-handler",
|
|
563
|
+
"crypto-helpers",
|
|
564
|
+
"form-validation-utils",
|
|
565
|
+
"tailwind-merge-utils",
|
|
566
|
+
"clsx-merge",
|
|
567
|
+
"date-fns-utils",
|
|
568
|
+
"dayjs-helpers",
|
|
569
|
+
"zod-helpers",
|
|
570
|
+
"yup-utils",
|
|
571
|
+
"joi-helpers",
|
|
572
|
+
"ajv-utils",
|
|
573
|
+
"docker-helpers",
|
|
574
|
+
"kubernetes-utils",
|
|
575
|
+
"terraform-helpers",
|
|
576
|
+
"graphql-helpers",
|
|
577
|
+
"apollo-utils",
|
|
578
|
+
"trpc-helpers",
|
|
579
|
+
"socket-io-helpers",
|
|
580
|
+
"websocket-utils",
|
|
581
|
+
"redis-helpers",
|
|
582
|
+
"kafka-utils",
|
|
583
|
+
"rabbitmq-helpers",
|
|
584
|
+
"aws-sdk-helpers",
|
|
585
|
+
"gcp-utils",
|
|
586
|
+
"azure-helpers",
|
|
587
|
+
"testing-utils-pro",
|
|
588
|
+
"jest-helpers",
|
|
589
|
+
"vitest-utils",
|
|
590
|
+
"playwright-helpers",
|
|
591
|
+
"cypress-utils"
|
|
592
|
+
]);
|
|
593
|
+
var KNOWN_MALICIOUS_PACKAGES = /* @__PURE__ */ new Set([
|
|
594
|
+
"event-stream",
|
|
595
|
+
// Compromised in 2018 — flatmap-stream injection
|
|
596
|
+
"ua-parser-js",
|
|
597
|
+
// Compromised versions 0.7.29, 0.8.0, 1.0.0
|
|
598
|
+
"coa",
|
|
599
|
+
// Hijacked Nov 2021
|
|
600
|
+
"rc",
|
|
601
|
+
// Hijacked Nov 2021
|
|
602
|
+
"colors",
|
|
603
|
+
// Sabotaged by maintainer Jan 2022
|
|
604
|
+
"faker",
|
|
605
|
+
// Sabotaged by maintainer Jan 2022
|
|
606
|
+
"node-ipc",
|
|
607
|
+
// Protestware (peacenotwar) Mar 2022
|
|
608
|
+
"es5-ext",
|
|
609
|
+
// Protestware
|
|
610
|
+
"crossenv",
|
|
611
|
+
// Typosquat of cross-env
|
|
612
|
+
"mongose",
|
|
613
|
+
// Typosquat of mongoose
|
|
614
|
+
"babelcli",
|
|
615
|
+
// Typosquat of babel-cli
|
|
616
|
+
"eslint-scope",
|
|
617
|
+
// Compromised Jul 2018
|
|
618
|
+
"flatmap-stream",
|
|
619
|
+
// Malicious — injected into event-stream
|
|
620
|
+
"getcookies",
|
|
621
|
+
// Malicious backdoor
|
|
622
|
+
"mailparser",
|
|
623
|
+
// Typosquat versions
|
|
624
|
+
"http-proxy.js",
|
|
625
|
+
// Typosquat of http-proxy
|
|
626
|
+
"discordi.js",
|
|
627
|
+
// Typosquat of discord.js
|
|
628
|
+
"discord.jss",
|
|
629
|
+
"twilio-npm",
|
|
630
|
+
// Typosquat
|
|
631
|
+
"loadyaml",
|
|
632
|
+
// Malicious
|
|
633
|
+
"lodashs",
|
|
634
|
+
// Typosquat of lodash
|
|
635
|
+
"lodahs",
|
|
636
|
+
"@aspect-build/rules_js"
|
|
637
|
+
// Dependency confusion PoC
|
|
638
|
+
]);
|
|
639
|
+
var POPULAR_PACKAGES = [
|
|
640
|
+
// Core utilities
|
|
641
|
+
"lodash",
|
|
642
|
+
"underscore",
|
|
643
|
+
"ramda",
|
|
644
|
+
"date-fns",
|
|
645
|
+
"dayjs",
|
|
646
|
+
"moment",
|
|
647
|
+
"uuid",
|
|
648
|
+
"nanoid",
|
|
649
|
+
"chalk",
|
|
650
|
+
"debug",
|
|
651
|
+
"dotenv",
|
|
652
|
+
"cross-env",
|
|
653
|
+
"rimraf",
|
|
654
|
+
"glob",
|
|
655
|
+
"minimatch",
|
|
656
|
+
"semver",
|
|
657
|
+
"yargs",
|
|
658
|
+
"commander",
|
|
659
|
+
"inquirer",
|
|
660
|
+
// Type checking / validation
|
|
661
|
+
"zod",
|
|
662
|
+
"yup",
|
|
663
|
+
"joi",
|
|
664
|
+
"ajv",
|
|
665
|
+
"io-ts",
|
|
666
|
+
"superstruct",
|
|
667
|
+
"typebox",
|
|
668
|
+
// React
|
|
669
|
+
"react",
|
|
670
|
+
"react-dom",
|
|
671
|
+
"react-router",
|
|
672
|
+
"react-router-dom",
|
|
673
|
+
"react-hook-form",
|
|
674
|
+
"react-query",
|
|
675
|
+
"@tanstack/react-query",
|
|
676
|
+
"react-select",
|
|
677
|
+
"react-modal",
|
|
678
|
+
"react-table",
|
|
679
|
+
"react-spring",
|
|
680
|
+
"framer-motion",
|
|
681
|
+
"react-icons",
|
|
682
|
+
"react-toastify",
|
|
683
|
+
"react-dropzone",
|
|
684
|
+
// State management
|
|
685
|
+
"redux",
|
|
686
|
+
"zustand",
|
|
687
|
+
"jotai",
|
|
688
|
+
"recoil",
|
|
689
|
+
"mobx",
|
|
690
|
+
"valtio",
|
|
691
|
+
"immer",
|
|
692
|
+
// Next.js
|
|
693
|
+
"next",
|
|
694
|
+
"next-auth",
|
|
695
|
+
"@next/font",
|
|
696
|
+
"@next/image",
|
|
697
|
+
"next-seo",
|
|
698
|
+
// Styling
|
|
699
|
+
"tailwindcss",
|
|
700
|
+
"postcss",
|
|
701
|
+
"autoprefixer",
|
|
702
|
+
"sass",
|
|
703
|
+
"styled-components",
|
|
704
|
+
"@emotion/react",
|
|
705
|
+
"@emotion/styled",
|
|
706
|
+
"clsx",
|
|
707
|
+
"classnames",
|
|
708
|
+
"tailwind-merge",
|
|
709
|
+
// Build / bundling
|
|
710
|
+
"webpack",
|
|
711
|
+
"vite",
|
|
712
|
+
"esbuild",
|
|
713
|
+
"rollup",
|
|
714
|
+
"turbo",
|
|
715
|
+
"tsup",
|
|
716
|
+
"unbuild",
|
|
717
|
+
"typescript",
|
|
718
|
+
"babel",
|
|
719
|
+
"@babel/core",
|
|
720
|
+
// Testing
|
|
721
|
+
"jest",
|
|
722
|
+
"vitest",
|
|
723
|
+
"mocha",
|
|
724
|
+
"chai",
|
|
725
|
+
"sinon",
|
|
726
|
+
"cypress",
|
|
727
|
+
"playwright",
|
|
728
|
+
"@testing-library/react",
|
|
729
|
+
"@testing-library/jest-dom",
|
|
730
|
+
// Server
|
|
731
|
+
"express",
|
|
732
|
+
"fastify",
|
|
733
|
+
"koa",
|
|
734
|
+
"hono",
|
|
735
|
+
"h3",
|
|
736
|
+
"cors",
|
|
737
|
+
"helmet",
|
|
738
|
+
"body-parser",
|
|
739
|
+
"cookie-parser",
|
|
740
|
+
"morgan",
|
|
741
|
+
"compression",
|
|
742
|
+
// Database / ORM
|
|
743
|
+
"prisma",
|
|
744
|
+
"@prisma/client",
|
|
745
|
+
"drizzle-orm",
|
|
746
|
+
"mongoose",
|
|
747
|
+
"sequelize",
|
|
748
|
+
"typeorm",
|
|
749
|
+
"knex",
|
|
750
|
+
"pg",
|
|
751
|
+
"mysql2",
|
|
752
|
+
"better-sqlite3",
|
|
753
|
+
"redis",
|
|
754
|
+
"ioredis",
|
|
755
|
+
// Auth
|
|
756
|
+
"passport",
|
|
757
|
+
"jsonwebtoken",
|
|
758
|
+
"bcrypt",
|
|
759
|
+
"bcryptjs",
|
|
760
|
+
"argon2",
|
|
761
|
+
// HTTP
|
|
762
|
+
"axios",
|
|
763
|
+
"node-fetch",
|
|
764
|
+
"got",
|
|
765
|
+
"ky",
|
|
766
|
+
"superagent",
|
|
767
|
+
"undici",
|
|
768
|
+
// Linting
|
|
769
|
+
"eslint",
|
|
770
|
+
"prettier",
|
|
771
|
+
"stylelint",
|
|
772
|
+
"oxlint",
|
|
773
|
+
"biome",
|
|
774
|
+
// AI / ML
|
|
775
|
+
"openai",
|
|
776
|
+
"@anthropic-ai/sdk",
|
|
777
|
+
"langchain",
|
|
778
|
+
"@langchain/core",
|
|
779
|
+
"ai",
|
|
780
|
+
// Payments
|
|
781
|
+
"stripe",
|
|
782
|
+
"@stripe/stripe-js",
|
|
783
|
+
// Cloud
|
|
784
|
+
"aws-sdk",
|
|
785
|
+
"@aws-sdk/client-s3",
|
|
786
|
+
"firebase",
|
|
787
|
+
"@google-cloud/storage",
|
|
788
|
+
// Misc popular
|
|
789
|
+
"sharp",
|
|
790
|
+
"jimp",
|
|
791
|
+
"puppeteer",
|
|
792
|
+
"cheerio",
|
|
793
|
+
"pdf-lib",
|
|
794
|
+
"xlsx",
|
|
795
|
+
"nodemailer",
|
|
796
|
+
"bull",
|
|
797
|
+
"bullmq",
|
|
798
|
+
"socket.io",
|
|
799
|
+
"ws",
|
|
800
|
+
"graphql",
|
|
801
|
+
"@apollo/client",
|
|
802
|
+
"@apollo/server",
|
|
803
|
+
"@trpc/server",
|
|
804
|
+
"@trpc/client",
|
|
805
|
+
"pino",
|
|
806
|
+
"winston",
|
|
807
|
+
"bunyan",
|
|
808
|
+
"p-limit",
|
|
809
|
+
"p-queue",
|
|
810
|
+
"p-retry",
|
|
811
|
+
"execa",
|
|
812
|
+
"zx",
|
|
813
|
+
"msw",
|
|
814
|
+
"nock",
|
|
815
|
+
"supertest",
|
|
816
|
+
"docker-compose",
|
|
817
|
+
"dockerode"
|
|
818
|
+
];
|
|
819
|
+
var PROVIDED_MODULES = /* @__PURE__ */ new Set(["vscode", "electron", "deno"]);
|
|
820
|
+
var DEFAULT_CONFIG = {
|
|
821
|
+
confidenceThreshold: 0.7,
|
|
822
|
+
registryConcurrency: 6,
|
|
823
|
+
registryTimeoutMs: 3e3,
|
|
824
|
+
registryCacheTtlMs: 36e5,
|
|
825
|
+
// 1 hour
|
|
826
|
+
registryNegativeTtlMs: 864e5,
|
|
827
|
+
// 24 hours
|
|
828
|
+
fsCacheTtlMs: 3e4,
|
|
829
|
+
// 30 seconds
|
|
830
|
+
enableTyposquatDetection: true,
|
|
831
|
+
typosquatMaxDistance: 2,
|
|
832
|
+
enableDepConfusionDetection: true,
|
|
833
|
+
enableLockfileAnalysis: true,
|
|
834
|
+
enableFileWatchers: false,
|
|
835
|
+
customRegistries: [],
|
|
836
|
+
additionalSafePackages: []
|
|
837
|
+
};
|
|
838
|
+
var LRUCache = class {
|
|
839
|
+
constructor(_maxSize = 2048, _defaultTtlMs = 6e4) {
|
|
840
|
+
this._maxSize = _maxSize;
|
|
841
|
+
this._defaultTtlMs = _defaultTtlMs;
|
|
842
|
+
}
|
|
843
|
+
_map = /* @__PURE__ */ new Map();
|
|
844
|
+
get(key) {
|
|
845
|
+
const entry = this._map.get(key);
|
|
846
|
+
if (!entry) return void 0;
|
|
847
|
+
if (Date.now() > entry.expiresAt) {
|
|
848
|
+
this._map.delete(key);
|
|
849
|
+
return void 0;
|
|
850
|
+
}
|
|
851
|
+
this._map.delete(key);
|
|
852
|
+
this._map.set(key, entry);
|
|
853
|
+
return entry.value;
|
|
854
|
+
}
|
|
855
|
+
set(key, value, ttlMs) {
|
|
856
|
+
if (this._map.has(key)) this._map.delete(key);
|
|
857
|
+
if (this._map.size >= this._maxSize) {
|
|
858
|
+
const oldest = this._map.keys().next().value;
|
|
859
|
+
if (oldest !== void 0) this._map.delete(oldest);
|
|
860
|
+
}
|
|
861
|
+
this._map.set(key, {
|
|
862
|
+
value,
|
|
863
|
+
expiresAt: Date.now() + (ttlMs ?? this._defaultTtlMs)
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
has(key) {
|
|
867
|
+
return this.get(key) !== void 0;
|
|
868
|
+
}
|
|
869
|
+
delete(key) {
|
|
870
|
+
this._map.delete(key);
|
|
871
|
+
}
|
|
872
|
+
clear() {
|
|
873
|
+
this._map.clear();
|
|
874
|
+
}
|
|
875
|
+
get size() {
|
|
876
|
+
return this._map.size;
|
|
877
|
+
}
|
|
878
|
+
/** Snapshot for persistence: [key, { value, expiresAt }][] */
|
|
879
|
+
entries() {
|
|
880
|
+
const now = Date.now();
|
|
881
|
+
return Array.from(this._map.entries()).filter(([, e]) => now <= e.expiresAt).map(([k, e]) => [k, { value: e.value, expiresAt: e.expiresAt }]);
|
|
882
|
+
}
|
|
883
|
+
/** Evict all expired entries */
|
|
884
|
+
prune() {
|
|
885
|
+
const now = Date.now();
|
|
886
|
+
let evicted = 0;
|
|
887
|
+
for (const [key, entry] of this._map) {
|
|
888
|
+
if (now > entry.expiresAt) {
|
|
889
|
+
this._map.delete(key);
|
|
890
|
+
evicted++;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return evicted;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
var MONOREPO_DIR_ALIASES = /* @__PURE__ */ new Set([
|
|
897
|
+
"test",
|
|
898
|
+
"tests",
|
|
899
|
+
"src",
|
|
900
|
+
"lib",
|
|
901
|
+
"app",
|
|
902
|
+
"server",
|
|
903
|
+
"client",
|
|
904
|
+
"shared",
|
|
905
|
+
"common",
|
|
906
|
+
"utils",
|
|
907
|
+
"config",
|
|
908
|
+
"scripts",
|
|
909
|
+
"build",
|
|
910
|
+
"docs",
|
|
911
|
+
"helpers",
|
|
912
|
+
"fixtures",
|
|
913
|
+
"mocks",
|
|
914
|
+
"stubs",
|
|
915
|
+
"support",
|
|
916
|
+
"tools",
|
|
917
|
+
"internal",
|
|
918
|
+
"core",
|
|
919
|
+
"api",
|
|
920
|
+
"models",
|
|
921
|
+
"views",
|
|
922
|
+
"controllers",
|
|
923
|
+
"middleware",
|
|
924
|
+
"routes",
|
|
925
|
+
"schemas",
|
|
926
|
+
"types",
|
|
927
|
+
"components",
|
|
928
|
+
"pages",
|
|
929
|
+
"layouts",
|
|
930
|
+
"styles",
|
|
931
|
+
"assets",
|
|
932
|
+
"public",
|
|
933
|
+
"static",
|
|
934
|
+
"vendor",
|
|
935
|
+
"plugins",
|
|
936
|
+
"modules",
|
|
937
|
+
"features"
|
|
938
|
+
]);
|
|
939
|
+
function damerauLevenshtein(a, b, maxDist) {
|
|
940
|
+
const m = a.length;
|
|
941
|
+
const n = b.length;
|
|
942
|
+
if (Math.abs(m - n) > maxDist) return maxDist + 1;
|
|
943
|
+
if (a === b) return 0;
|
|
944
|
+
if (m === 0) return n;
|
|
945
|
+
if (n === 0) return m;
|
|
946
|
+
if (m === 1 && n === 1) return a === b ? 0 : 1;
|
|
947
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
948
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
949
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
950
|
+
for (let i = 1; i <= m; i++) {
|
|
951
|
+
let rowMin = Infinity;
|
|
952
|
+
for (let j = 1; j <= n; j++) {
|
|
953
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
954
|
+
dp[i][j] = Math.min(
|
|
955
|
+
dp[i - 1][j] + 1,
|
|
956
|
+
// deletion
|
|
957
|
+
dp[i][j - 1] + 1,
|
|
958
|
+
// insertion
|
|
959
|
+
dp[i - 1][j - 1] + cost
|
|
960
|
+
// substitution
|
|
961
|
+
);
|
|
962
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
963
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + cost);
|
|
964
|
+
}
|
|
965
|
+
rowMin = Math.min(rowMin, dp[i][j]);
|
|
966
|
+
}
|
|
967
|
+
if (rowMin > maxDist) return maxDist + 1;
|
|
968
|
+
}
|
|
969
|
+
return dp[m][n];
|
|
970
|
+
}
|
|
971
|
+
function isVisuallyConfusable(candidate, real) {
|
|
972
|
+
const norm = (s) => s.replace(/[-_.]/g, "").toLowerCase();
|
|
973
|
+
if (norm(candidate) === norm(real) && candidate !== real) return true;
|
|
974
|
+
const homoglyphs = [
|
|
975
|
+
["0", "o"],
|
|
976
|
+
["1", "l"],
|
|
977
|
+
["1", "i"],
|
|
978
|
+
["rn", "m"],
|
|
979
|
+
["vv", "w"],
|
|
980
|
+
["cl", "d"],
|
|
981
|
+
["nn", "m"]
|
|
982
|
+
];
|
|
983
|
+
let normed = candidate.toLowerCase();
|
|
984
|
+
for (const [from, to] of homoglyphs) {
|
|
985
|
+
normed = normed.replaceAll(from, to);
|
|
986
|
+
}
|
|
987
|
+
if (normed === real.toLowerCase()) return true;
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
function isNpmLikePackageName(pkg) {
|
|
991
|
+
if (pkg.startsWith("@")) {
|
|
992
|
+
return /^@[a-z][a-z0-9._-]+\/[a-z]/.test(pkg);
|
|
993
|
+
}
|
|
994
|
+
return /^[a-z0-9][a-z0-9._-]*$/.test(pkg);
|
|
995
|
+
}
|
|
996
|
+
function findTyposquatMatch(pkg, maxDistance) {
|
|
997
|
+
if (pkg.length < 3) return null;
|
|
998
|
+
if (pkg.includes("/") && !pkg.startsWith("@")) return null;
|
|
999
|
+
if (!isNpmLikePackageName(pkg)) return null;
|
|
1000
|
+
const isScoped = pkg.startsWith("@");
|
|
1001
|
+
let bestMatch = null;
|
|
1002
|
+
for (const real of POPULAR_PACKAGES) {
|
|
1003
|
+
if (pkg === real) return null;
|
|
1004
|
+
const realIsScoped = real.startsWith("@");
|
|
1005
|
+
if (isScoped !== realIsScoped) continue;
|
|
1006
|
+
if (Math.abs(pkg.length - real.length) > maxDistance) continue;
|
|
1007
|
+
const dist = damerauLevenshtein(pkg, real, maxDistance);
|
|
1008
|
+
const visual = isVisuallyConfusable(pkg, real);
|
|
1009
|
+
if (dist <= maxDistance || visual) {
|
|
1010
|
+
const effective = visual ? Math.min(dist, 1) : dist;
|
|
1011
|
+
if (!bestMatch || effective < bestMatch.distance) {
|
|
1012
|
+
bestMatch = {
|
|
1013
|
+
realPackage: real,
|
|
1014
|
+
distance: dist,
|
|
1015
|
+
visuallyConfusable: visual
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return bestMatch;
|
|
1021
|
+
}
|
|
1022
|
+
function detectLockfileType(workspaceRoot) {
|
|
1023
|
+
const checks = [
|
|
1024
|
+
{ file: "pnpm-lock.yaml", type: "pnpm" },
|
|
1025
|
+
{ file: "bun.lockb", type: "bun" },
|
|
1026
|
+
{ file: "yarn.lock", type: "yarn-classic" },
|
|
1027
|
+
// or berry — distinguished by content
|
|
1028
|
+
{ file: "package-lock.json", type: "npm" }
|
|
1029
|
+
];
|
|
1030
|
+
for (const { file, type } of checks) {
|
|
1031
|
+
const filePath = join(workspaceRoot, file);
|
|
1032
|
+
if (existsSync(filePath)) {
|
|
1033
|
+
try {
|
|
1034
|
+
const stat = statSync(filePath);
|
|
1035
|
+
return { type, path: filePath, packages: /* @__PURE__ */ new Set(), mtime: stat.mtimeMs };
|
|
1036
|
+
} catch (err) {
|
|
1037
|
+
if (process.env.VIBECHECK_DEBUG) {
|
|
1038
|
+
console.warn(`[phantom_dep] statSync failed for ${filePath}:`, err instanceof Error ? err.message : err);
|
|
1039
|
+
}
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
function parseLockfilePackages(info) {
|
|
1047
|
+
const packages = /* @__PURE__ */ new Set();
|
|
1048
|
+
try {
|
|
1049
|
+
const content = readFileSync(info.path, "utf-8");
|
|
1050
|
+
switch (info.type) {
|
|
1051
|
+
case "npm": {
|
|
1052
|
+
const lock = JSON.parse(content);
|
|
1053
|
+
const pkgs = typeof lock === "object" && lock !== null ? lock.packages ?? lock.dependencies ?? {} : {};
|
|
1054
|
+
if (typeof pkgs !== "object" || pkgs === null) break;
|
|
1055
|
+
for (const key of Object.keys(pkgs)) {
|
|
1056
|
+
const name = key.replace(/^node_modules\//, "");
|
|
1057
|
+
if (name && !name.includes("node_modules/")) {
|
|
1058
|
+
packages.add(name);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
case "yarn-classic":
|
|
1064
|
+
case "yarn-berry": {
|
|
1065
|
+
const lineRe = /^"?(@?[^@\s"]+)@/gm;
|
|
1066
|
+
let m;
|
|
1067
|
+
while ((m = lineRe.exec(content)) !== null) {
|
|
1068
|
+
const name = m[1];
|
|
1069
|
+
if (name && !name.startsWith("__")) packages.add(name);
|
|
1070
|
+
}
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
case "pnpm": {
|
|
1074
|
+
const lineRe = /^\s+\/?(@?[^@/\s:]+(?:\/[^@/\s:]+)?)[@/]\d/gm;
|
|
1075
|
+
let m;
|
|
1076
|
+
while ((m = lineRe.exec(content)) !== null) {
|
|
1077
|
+
if (m[1]) packages.add(m[1]);
|
|
1078
|
+
}
|
|
1079
|
+
const depRe = /^\s{2,8}(@?[\w][\w./-]*?):\s/gm;
|
|
1080
|
+
while ((m = depRe.exec(content)) !== null) {
|
|
1081
|
+
const name = m[1];
|
|
1082
|
+
if (name && !name.startsWith("//") && name.includes("/") || /^@?[a-z]/.test(name)) {
|
|
1083
|
+
packages.add(name);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
case "bun": {
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
return packages;
|
|
1095
|
+
}
|
|
1096
|
+
function resolveWorkspaceGlobs(root, globs) {
|
|
1097
|
+
const dirs = [];
|
|
1098
|
+
for (const glob of globs) {
|
|
1099
|
+
const clean = glob.replace(/\/?\*\*?$/, "");
|
|
1100
|
+
const base = join(root, clean);
|
|
1101
|
+
if (!existsSync(base)) continue;
|
|
1102
|
+
try {
|
|
1103
|
+
const entries = readdirSync(base, { withFileTypes: true });
|
|
1104
|
+
for (const entry of entries) {
|
|
1105
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
1106
|
+
const pkgPath = join(base, entry.name, "package.json");
|
|
1107
|
+
if (existsSync(pkgPath)) {
|
|
1108
|
+
dirs.push(join(base, entry.name));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return dirs;
|
|
1116
|
+
}
|
|
1117
|
+
function loadWorkspaceInfo(root) {
|
|
1118
|
+
const info = {
|
|
1119
|
+
root,
|
|
1120
|
+
packageDirs: [],
|
|
1121
|
+
allPackageNames: /* @__PURE__ */ new Set(),
|
|
1122
|
+
allDeps: /* @__PURE__ */ new Set(),
|
|
1123
|
+
lockfile: null,
|
|
1124
|
+
tsconfigAliases: /* @__PURE__ */ new Set(),
|
|
1125
|
+
workspaceProtocol: /* @__PURE__ */ new Set(),
|
|
1126
|
+
packageJsonImports: /* @__PURE__ */ new Set()
|
|
1127
|
+
};
|
|
1128
|
+
const rootPkgPath = join(root, "package.json");
|
|
1129
|
+
if (existsSync(rootPkgPath)) {
|
|
1130
|
+
try {
|
|
1131
|
+
const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
|
|
1132
|
+
const workspaces = rootPkg.workspaces;
|
|
1133
|
+
const globs = Array.isArray(workspaces) ? workspaces : Array.isArray(workspaces?.packages) ? workspaces.packages : null;
|
|
1134
|
+
if (globs) {
|
|
1135
|
+
info.packageDirs = resolveWorkspaceGlobs(root, globs);
|
|
1136
|
+
}
|
|
1137
|
+
collectDeps(rootPkg, info.allDeps, info.workspaceProtocol);
|
|
1138
|
+
collectPackageJsonImports(rootPkg, info.packageJsonImports);
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
const pnpmWs = join(root, "pnpm-workspace.yaml");
|
|
1143
|
+
if (existsSync(pnpmWs) && info.packageDirs.length === 0) {
|
|
1144
|
+
try {
|
|
1145
|
+
const content = readFileSync(pnpmWs, "utf-8");
|
|
1146
|
+
const globs = [];
|
|
1147
|
+
const re = /^\s*-\s+['"]?([^'"#\n]+)/gm;
|
|
1148
|
+
let m;
|
|
1149
|
+
while ((m = re.exec(content)) !== null) {
|
|
1150
|
+
globs.push(m[1].trim());
|
|
1151
|
+
}
|
|
1152
|
+
info.packageDirs = resolveWorkspaceGlobs(root, globs);
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (info.packageDirs.length === 0) {
|
|
1157
|
+
for (const subdir of ["packages", "apps", "libs", "modules", "services", "tools"]) {
|
|
1158
|
+
const dir = join(root, subdir);
|
|
1159
|
+
if (existsSync(dir)) {
|
|
1160
|
+
try {
|
|
1161
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1162
|
+
if (entry.isDirectory() && existsSync(join(dir, entry.name, "package.json"))) {
|
|
1163
|
+
info.packageDirs.push(join(dir, entry.name));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
for (const dir of info.packageDirs) {
|
|
1172
|
+
const pkgPath = join(dir, "package.json");
|
|
1173
|
+
try {
|
|
1174
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1175
|
+
if (pkg.name) info.allPackageNames.add(pkg.name);
|
|
1176
|
+
collectDeps(pkg, info.allDeps, info.workspaceProtocol);
|
|
1177
|
+
collectPackageJsonImports(pkg, info.packageJsonImports);
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
info.lockfile = detectLockfileType(root);
|
|
1182
|
+
if (info.lockfile) {
|
|
1183
|
+
const lockPkgs = parseLockfilePackages(info.lockfile);
|
|
1184
|
+
lockPkgs.forEach((p) => info.allDeps.add(p));
|
|
1185
|
+
info.lockfile.packages = lockPkgs;
|
|
1186
|
+
}
|
|
1187
|
+
info.tsconfigAliases = loadTsconfigPaths(root);
|
|
1188
|
+
return info;
|
|
1189
|
+
}
|
|
1190
|
+
function collectDeps(pkg, deps, wsProtocol) {
|
|
1191
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
1192
|
+
const sectionDeps = pkg[section];
|
|
1193
|
+
if (!sectionDeps || typeof sectionDeps !== "object") continue;
|
|
1194
|
+
for (const [name, version] of Object.entries(sectionDeps)) {
|
|
1195
|
+
deps.add(name);
|
|
1196
|
+
if (typeof version === "string" && version.startsWith("workspace:")) {
|
|
1197
|
+
wsProtocol.add(name);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function collectPackageJsonImports(pkg, imports) {
|
|
1203
|
+
const importsField = pkg.imports;
|
|
1204
|
+
if (!importsField || typeof importsField !== "object") return;
|
|
1205
|
+
for (const key of Object.keys(importsField)) {
|
|
1206
|
+
const alias = key.replace(/\/?\*$/, "");
|
|
1207
|
+
if (alias) imports.add(alias);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
function loadTsconfigPaths(root, visited = /* @__PURE__ */ new Set()) {
|
|
1211
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
1212
|
+
for (const file of ["tsconfig.json", "tsconfig.base.json", "jsconfig.json"]) {
|
|
1213
|
+
const filePath = join(root, file);
|
|
1214
|
+
if (!existsSync(filePath)) continue;
|
|
1215
|
+
const resolvedPath = resolve(filePath);
|
|
1216
|
+
if (visited.has(resolvedPath)) break;
|
|
1217
|
+
visited.add(resolvedPath);
|
|
1218
|
+
try {
|
|
1219
|
+
let content = readFileSync(filePath, "utf-8");
|
|
1220
|
+
content = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1221
|
+
content = content.replace(/,\s*([\]}])/g, "$1");
|
|
1222
|
+
const config = JSON.parse(content);
|
|
1223
|
+
const paths = config.compilerOptions?.paths ?? {};
|
|
1224
|
+
for (const pattern of Object.keys(paths)) {
|
|
1225
|
+
const alias = pattern.replace(/\/?\*$/, "");
|
|
1226
|
+
if (alias) aliases.add(alias);
|
|
1227
|
+
}
|
|
1228
|
+
if (config.extends) {
|
|
1229
|
+
const extPath = resolve(root, config.extends);
|
|
1230
|
+
if (existsSync(extPath)) {
|
|
1231
|
+
const extAliases = loadTsconfigPaths(dirname(extPath), visited);
|
|
1232
|
+
extAliases.forEach((a) => aliases.add(a));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
} catch {
|
|
1236
|
+
}
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
return aliases;
|
|
1240
|
+
}
|
|
1241
|
+
var RegistryClient = class {
|
|
1242
|
+
constructor(_config) {
|
|
1243
|
+
this._config = _config;
|
|
1244
|
+
this._cache = new LRUCache(4096, _config.registryCacheTtlMs);
|
|
1245
|
+
if (this._config.registryCachePath) {
|
|
1246
|
+
this._loadFromDisk();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
_cache;
|
|
1250
|
+
_inflightRequests = /* @__PURE__ */ new Map();
|
|
1251
|
+
_queue = [];
|
|
1252
|
+
_activeRequests = 0;
|
|
1253
|
+
_persistTimer = null;
|
|
1254
|
+
_loadFromDisk() {
|
|
1255
|
+
const p = this._config.registryCachePath;
|
|
1256
|
+
if (!existsSync(p)) return;
|
|
1257
|
+
try {
|
|
1258
|
+
const raw = readFileSync(p, "utf-8");
|
|
1259
|
+
const data = JSON.parse(raw);
|
|
1260
|
+
const now = Date.now();
|
|
1261
|
+
for (const [pkg, entry] of Object.entries(data)) {
|
|
1262
|
+
if (entry.expiresAt > now) {
|
|
1263
|
+
const ttl = entry.expiresAt - now;
|
|
1264
|
+
this._cache.set(pkg, entry.exists, ttl);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
_schedulePersist() {
|
|
1271
|
+
if (!this._config.registryCachePath) return;
|
|
1272
|
+
if (this._persistTimer) return;
|
|
1273
|
+
this._persistTimer = setTimeout(() => {
|
|
1274
|
+
this._persistTimer = null;
|
|
1275
|
+
this._persistToDisk();
|
|
1276
|
+
}, 500);
|
|
1277
|
+
}
|
|
1278
|
+
_persistToDisk() {
|
|
1279
|
+
const p = this._config.registryCachePath;
|
|
1280
|
+
try {
|
|
1281
|
+
const entries = this._cache.entries();
|
|
1282
|
+
const data = {};
|
|
1283
|
+
for (const [k, e] of entries) {
|
|
1284
|
+
data[k] = { exists: e.value, expiresAt: e.expiresAt };
|
|
1285
|
+
}
|
|
1286
|
+
const dir = dirname(p);
|
|
1287
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1288
|
+
writeFileSync(p, JSON.stringify(data), "utf-8");
|
|
1289
|
+
} catch {
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/** Wait for all in-flight registry requests and queue to drain. */
|
|
1293
|
+
async waitForIdle() {
|
|
1294
|
+
while (this._queue.length > 0 || this._inflightRequests.size > 0) {
|
|
1295
|
+
const promises = Array.from(this._inflightRequests.values());
|
|
1296
|
+
if (promises.length > 0) {
|
|
1297
|
+
await Promise.all(promises);
|
|
1298
|
+
} else {
|
|
1299
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
/** Flush pending persist immediately (e.g. on engine dispose). */
|
|
1304
|
+
flushPersist() {
|
|
1305
|
+
if (this._persistTimer) {
|
|
1306
|
+
clearTimeout(this._persistTimer);
|
|
1307
|
+
this._persistTimer = null;
|
|
1308
|
+
}
|
|
1309
|
+
if (this._config.registryCachePath) this._persistToDisk();
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Check if a package exists on the registry.
|
|
1313
|
+
* Returns immediately if cached; otherwise queues an async check.
|
|
1314
|
+
*/
|
|
1315
|
+
check(pkg) {
|
|
1316
|
+
const cached = this._cache.get(pkg);
|
|
1317
|
+
if (cached !== void 0) {
|
|
1318
|
+
return { exists: cached, checkedAt: Date.now(), fromCache: true };
|
|
1319
|
+
}
|
|
1320
|
+
this._enqueue(pkg);
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Await a definitive answer for a specific package.
|
|
1325
|
+
*/
|
|
1326
|
+
async checkAsync(pkg) {
|
|
1327
|
+
const cached = this._cache.get(pkg);
|
|
1328
|
+
if (cached !== void 0) {
|
|
1329
|
+
return { exists: cached, checkedAt: Date.now(), fromCache: true };
|
|
1330
|
+
}
|
|
1331
|
+
const exists = await this._fetchSingle(pkg);
|
|
1332
|
+
return { exists, checkedAt: Date.now(), fromCache: false };
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Batch check — resolves when all packages are checked.
|
|
1336
|
+
* Deduplicates concurrent requests for the same package via _inflightRequests.
|
|
1337
|
+
*/
|
|
1338
|
+
async batchCheck(packages) {
|
|
1339
|
+
const results = /* @__PURE__ */ new Map();
|
|
1340
|
+
const uncached = [];
|
|
1341
|
+
for (const pkg of packages) {
|
|
1342
|
+
const cached = this._cache.get(pkg);
|
|
1343
|
+
if (cached !== void 0) {
|
|
1344
|
+
results.set(pkg, cached);
|
|
1345
|
+
} else if (this._inflightRequests.has(pkg)) {
|
|
1346
|
+
const exists = await this._inflightRequests.get(pkg);
|
|
1347
|
+
results.set(pkg, exists);
|
|
1348
|
+
} else {
|
|
1349
|
+
uncached.push(pkg);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
if (uncached.length === 0) return results;
|
|
1353
|
+
const batches = [];
|
|
1354
|
+
for (let i = 0; i < uncached.length; i += this._config.registryConcurrency) {
|
|
1355
|
+
batches.push(uncached.slice(i, i + this._config.registryConcurrency));
|
|
1356
|
+
}
|
|
1357
|
+
for (const batch of batches) {
|
|
1358
|
+
const batchResults = await Promise.allSettled(
|
|
1359
|
+
batch.map(async (pkg) => {
|
|
1360
|
+
const promise = this._fetchSingle(pkg).finally(() => {
|
|
1361
|
+
this._inflightRequests.delete(pkg);
|
|
1362
|
+
});
|
|
1363
|
+
this._inflightRequests.set(pkg, promise);
|
|
1364
|
+
const exists = await promise;
|
|
1365
|
+
return { pkg, exists };
|
|
1366
|
+
})
|
|
1367
|
+
);
|
|
1368
|
+
for (const result of batchResults) {
|
|
1369
|
+
if (result.status === "fulfilled") {
|
|
1370
|
+
results.set(result.value.pkg, result.value.exists);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return results;
|
|
1375
|
+
}
|
|
1376
|
+
_enqueue(pkg) {
|
|
1377
|
+
if (this._inflightRequests.has(pkg)) return;
|
|
1378
|
+
this._queue.push(pkg);
|
|
1379
|
+
this._processQueue();
|
|
1380
|
+
}
|
|
1381
|
+
_processQueue() {
|
|
1382
|
+
while (this._queue.length > 0 && this._activeRequests < this._config.registryConcurrency) {
|
|
1383
|
+
const pkg = this._queue.shift();
|
|
1384
|
+
if (this._cache.has(pkg) || this._inflightRequests.has(pkg)) continue;
|
|
1385
|
+
this._activeRequests++;
|
|
1386
|
+
const promise = this._fetchSingle(pkg).finally(() => {
|
|
1387
|
+
this._activeRequests--;
|
|
1388
|
+
this._inflightRequests.delete(pkg);
|
|
1389
|
+
this._processQueue();
|
|
1390
|
+
});
|
|
1391
|
+
this._inflightRequests.set(pkg, promise);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
async _fetchSingle(pkg) {
|
|
1395
|
+
const registries = ["https://registry.npmjs.org", ...this._config.customRegistries];
|
|
1396
|
+
for (const registry of registries) {
|
|
1397
|
+
try {
|
|
1398
|
+
const exists = await this._fetchWithRetry(pkg, registry);
|
|
1399
|
+
const ttl = exists ? this._config.registryCacheTtlMs : this._config.registryNegativeTtlMs;
|
|
1400
|
+
this._cache.set(pkg, exists, ttl);
|
|
1401
|
+
this._schedulePersist();
|
|
1402
|
+
return exists;
|
|
1403
|
+
} catch {
|
|
1404
|
+
this._cache.set(pkg, true, 6e4);
|
|
1405
|
+
this._schedulePersist();
|
|
1406
|
+
return true;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return true;
|
|
1410
|
+
}
|
|
1411
|
+
async _fetchWithRetry(pkg, registry) {
|
|
1412
|
+
const maxRetries = 3;
|
|
1413
|
+
const baseDelay = 1e3;
|
|
1414
|
+
let lastError = null;
|
|
1415
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1416
|
+
try {
|
|
1417
|
+
const url = `${registry}/${pkg.startsWith("@") ? pkg : encodeURIComponent(pkg)}`;
|
|
1418
|
+
const controller = new AbortController();
|
|
1419
|
+
const timer = setTimeout(() => controller.abort(), this._config.registryTimeoutMs);
|
|
1420
|
+
const response = await fetch(url, {
|
|
1421
|
+
method: "HEAD",
|
|
1422
|
+
signal: controller.signal,
|
|
1423
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
1424
|
+
});
|
|
1425
|
+
clearTimeout(timer);
|
|
1426
|
+
return response.status === 200;
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1429
|
+
if (lastError.name === "AbortError" || lastError instanceof TypeError && lastError.message.includes("fetch")) {
|
|
1430
|
+
throw lastError;
|
|
1431
|
+
}
|
|
1432
|
+
if (attempt === maxRetries) {
|
|
1433
|
+
throw lastError;
|
|
1434
|
+
}
|
|
1435
|
+
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
|
|
1436
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
throw lastError || new Error("Max retries exceeded");
|
|
1440
|
+
}
|
|
1441
|
+
get cacheSize() {
|
|
1442
|
+
return this._cache.size;
|
|
1443
|
+
}
|
|
1444
|
+
clearCache() {
|
|
1445
|
+
this._cache.clear();
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
function computeConfidence(ctx) {
|
|
1449
|
+
const signals = [];
|
|
1450
|
+
let score = 0;
|
|
1451
|
+
if (ctx.inKnownFakeList) {
|
|
1452
|
+
signals.push({ source: "known_fake_list", weight: 0.5, detail: "Package is in the known-hallucinated-packages database" });
|
|
1453
|
+
score += 0.5;
|
|
1454
|
+
}
|
|
1455
|
+
if (ctx.inMaliciousList) {
|
|
1456
|
+
signals.push({ source: "known_malicious", weight: 0.55, detail: "Package is in the known-malicious-packages database" });
|
|
1457
|
+
score += 0.55;
|
|
1458
|
+
}
|
|
1459
|
+
if (ctx.registryExists === false) {
|
|
1460
|
+
signals.push({ source: "registry_404", weight: 0.45, detail: "npm registry returned 404 \u2014 package not found" });
|
|
1461
|
+
score += 0.45;
|
|
1462
|
+
}
|
|
1463
|
+
if (ctx.typosquatMatch) {
|
|
1464
|
+
const typoWeight = ctx.typosquatMatch.visuallyConfusable ? 0.3 : 0.2;
|
|
1465
|
+
signals.push({
|
|
1466
|
+
source: "typosquat",
|
|
1467
|
+
weight: typoWeight,
|
|
1468
|
+
detail: `Edit distance ${ctx.typosquatMatch.distance} from "${ctx.typosquatMatch.realPackage}"${ctx.typosquatMatch.visuallyConfusable ? " (visually confusable)" : ""}`
|
|
1469
|
+
});
|
|
1470
|
+
score += typoWeight;
|
|
1471
|
+
}
|
|
1472
|
+
if (!ctx.inPackageJson) {
|
|
1473
|
+
signals.push({ source: "not_in_package_json", weight: 0.15, detail: "Not declared in any package.json" });
|
|
1474
|
+
score += 0.15;
|
|
1475
|
+
}
|
|
1476
|
+
if (!ctx.inNodeModules) {
|
|
1477
|
+
signals.push({ source: "not_in_node_modules", weight: 0.1, detail: "Not found in node_modules" });
|
|
1478
|
+
score += 0.1;
|
|
1479
|
+
}
|
|
1480
|
+
if (!ctx.inLockfile) {
|
|
1481
|
+
signals.push({ source: "not_in_lockfile", weight: 0.1, detail: "Not present in lockfile" });
|
|
1482
|
+
score += 0.1;
|
|
1483
|
+
}
|
|
1484
|
+
if (ctx.inPackageJson) {
|
|
1485
|
+
signals.push({ source: "in_package_json", weight: -0.4, detail: "Declared in package.json" });
|
|
1486
|
+
score -= 0.4;
|
|
1487
|
+
}
|
|
1488
|
+
if (ctx.inNodeModules) {
|
|
1489
|
+
signals.push({ source: "in_node_modules", weight: -0.35, detail: "Found in node_modules" });
|
|
1490
|
+
score -= 0.35;
|
|
1491
|
+
}
|
|
1492
|
+
if (ctx.inLockfile) {
|
|
1493
|
+
signals.push({ source: "in_lockfile", weight: -0.3, detail: "Present in lockfile" });
|
|
1494
|
+
score -= 0.3;
|
|
1495
|
+
}
|
|
1496
|
+
if (ctx.registryExists === true && !ctx.inKnownFakeList && !ctx.inMaliciousList) {
|
|
1497
|
+
signals.push({ source: "registry_200", weight: -0.45, detail: "npm registry confirms package exists" });
|
|
1498
|
+
score -= 0.45;
|
|
1499
|
+
}
|
|
1500
|
+
return { score: Math.max(0, Math.min(1, score)), signals };
|
|
1501
|
+
}
|
|
1502
|
+
function classifyCategory(ctx) {
|
|
1503
|
+
if (ctx.inMaliciousList) return "known_malicious";
|
|
1504
|
+
if (ctx.typosquatMatch && ctx.registryExists === false) return "typosquat";
|
|
1505
|
+
if (ctx.isScopedPackage && ctx.hasPrivateScope && ctx.registryExists === true) return "dep_confusion";
|
|
1506
|
+
if (ctx.registryExists === true && !ctx.inPackageJson) return "undeclared_dep";
|
|
1507
|
+
return "phantom_dep";
|
|
1508
|
+
}
|
|
1509
|
+
function classifySeverity(category, confidence) {
|
|
1510
|
+
if (category === "known_malicious") return "critical";
|
|
1511
|
+
if (category === "phantom_dep" && confidence >= 0.9) return "critical";
|
|
1512
|
+
if (category === "typosquat") return "high";
|
|
1513
|
+
if (category === "dep_confusion") return "high";
|
|
1514
|
+
if (category === "phantom_dep" && confidence >= 0.75) return "high";
|
|
1515
|
+
if (category === "undeclared_dep") return "medium";
|
|
1516
|
+
return "low";
|
|
1517
|
+
}
|
|
1518
|
+
function ruleIdFor(category) {
|
|
1519
|
+
switch (category) {
|
|
1520
|
+
case "phantom_dep":
|
|
1521
|
+
return "PHANTOM-001";
|
|
1522
|
+
case "typosquat":
|
|
1523
|
+
return "TYPO-001";
|
|
1524
|
+
case "dep_confusion":
|
|
1525
|
+
return "DEPCONF-001";
|
|
1526
|
+
case "known_malicious":
|
|
1527
|
+
return "MALWARE-001";
|
|
1528
|
+
case "undeclared_dep":
|
|
1529
|
+
return "UNDECL-001";
|
|
1530
|
+
case "hallucinated_api":
|
|
1531
|
+
return "HALAPI-001";
|
|
1532
|
+
default:
|
|
1533
|
+
return "UNKNOWN-001";
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function messageFor(category, pkg, typoMatch) {
|
|
1537
|
+
switch (category) {
|
|
1538
|
+
case "phantom_dep":
|
|
1539
|
+
return `\`${pkg}\` is a hallucinated package \u2014 does not exist on npm. Likely invented by an AI coding assistant.`;
|
|
1540
|
+
case "typosquat":
|
|
1541
|
+
return `\`${pkg}\` appears to be a typosquat of \`${typoMatch?.realPackage}\` (edit distance: ${typoMatch?.distance}). Did you mean \`${typoMatch?.realPackage}\`?`;
|
|
1542
|
+
case "dep_confusion":
|
|
1543
|
+
return `\`${pkg}\` uses a private scope but a package with this name exists on the public npm registry. Potential dependency confusion attack vector.`;
|
|
1544
|
+
case "known_malicious":
|
|
1545
|
+
return `\`${pkg}\` is a known malicious package. Remove it immediately and audit your lockfile for compromised versions.`;
|
|
1546
|
+
case "undeclared_dep":
|
|
1547
|
+
return `\`${pkg}\` exists on npm but is not declared in package.json. This import relies on phantom hoisting and will break in strict package managers (pnpm).`;
|
|
1548
|
+
case "hallucinated_api":
|
|
1549
|
+
return `\`${pkg}\` exists but the imported API does not match its exports. The import may have been hallucinated by an AI coding assistant.`;
|
|
1550
|
+
default:
|
|
1551
|
+
return `\`${pkg}\` \u2014 unclassified dependency issue.`;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
function suggestionFor(category, pkg, typoMatch) {
|
|
1555
|
+
switch (category) {
|
|
1556
|
+
case "phantom_dep":
|
|
1557
|
+
return `Verify this package exists at https://www.npmjs.com/package/${pkg}. This name is commonly hallucinated by AI tools \u2014 search npm for the real equivalent.`;
|
|
1558
|
+
case "typosquat":
|
|
1559
|
+
return `Replace with the correct package: \`${typoMatch?.realPackage}\`. Run: npm install ${typoMatch?.realPackage}`;
|
|
1560
|
+
case "dep_confusion":
|
|
1561
|
+
return `Configure your .npmrc to scope this package to your private registry. Add: @${pkg.split("/")[0]?.slice(1)}:registry=https://your-private-registry.com`;
|
|
1562
|
+
case "known_malicious":
|
|
1563
|
+
return `Run: npm uninstall ${pkg} && npm audit. Check https://socket.dev/npm/package/${pkg} for the security advisory.`;
|
|
1564
|
+
case "undeclared_dep":
|
|
1565
|
+
return `Add to package.json: npm install ${pkg}. Undeclared deps fail in pnpm/yarn PnP strict mode.`;
|
|
1566
|
+
case "hallucinated_api":
|
|
1567
|
+
return `Check the package README for correct import syntax: https://www.npmjs.com/package/${pkg}`;
|
|
1568
|
+
default:
|
|
1569
|
+
return `Review this dependency.`;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
var PhantomDepEngine = class {
|
|
1573
|
+
constructor(_workspaceRoot, configOrThreshold = {}) {
|
|
1574
|
+
this._workspaceRoot = _workspaceRoot;
|
|
1575
|
+
const config = typeof configOrThreshold === "number" ? { confidenceThreshold: configOrThreshold } : configOrThreshold;
|
|
1576
|
+
this._config = { ...DEFAULT_CONFIG, ...config };
|
|
1577
|
+
this._registry = new RegistryClient(this._config);
|
|
1578
|
+
this._fsCache = new LRUCache(512, this._config.fsCacheTtlMs);
|
|
1579
|
+
this._loadWorkspace();
|
|
1580
|
+
}
|
|
1581
|
+
id = "phantom_dep";
|
|
1582
|
+
_config;
|
|
1583
|
+
_workspace = null;
|
|
1584
|
+
_registry;
|
|
1585
|
+
_fsCache;
|
|
1586
|
+
_watchedFiles = /* @__PURE__ */ new Set();
|
|
1587
|
+
_stats = {
|
|
1588
|
+
scansCompleted: 0,
|
|
1589
|
+
findingsTotal: 0,
|
|
1590
|
+
cacheHitRate: 0,
|
|
1591
|
+
avgScanMs: 0,
|
|
1592
|
+
registryChecks: 0,
|
|
1593
|
+
registryCacheSize: 0
|
|
1594
|
+
};
|
|
1595
|
+
_eventEmitter = new EventEmitter();
|
|
1596
|
+
_cacheHits = 0;
|
|
1597
|
+
_cacheMisses = 0;
|
|
1598
|
+
_scanTimesMs = [];
|
|
1599
|
+
/**
|
|
1600
|
+
* NPM phantom checks only apply to JS/TS. Python/Go profiles exist for workspace classification;
|
|
1601
|
+
* PyPI/proxy wiring must not call the npm registry with module names.
|
|
1602
|
+
*/
|
|
1603
|
+
_extractImports(delta, source) {
|
|
1604
|
+
const profile = getLanguageProfile(delta) ?? javascriptLanguageProfile;
|
|
1605
|
+
if (profile.id !== "javascript") return [];
|
|
1606
|
+
return profile.extractImportSpecifiers(source, delta.lines);
|
|
1607
|
+
}
|
|
1608
|
+
// ── Public API ──
|
|
1609
|
+
/**
|
|
1610
|
+
* Scan for phantom dependencies with standardized error handling
|
|
1611
|
+
*/
|
|
1612
|
+
async scan(delta, signal) {
|
|
1613
|
+
return withEngineErrorHandling(
|
|
1614
|
+
() => this._scanInternal(delta, signal),
|
|
1615
|
+
createContext(this.id, delta.documentUri)
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
async _scanInternal(delta, signal) {
|
|
1619
|
+
if (signal?.aborted) return [];
|
|
1620
|
+
const source = delta.fullText;
|
|
1621
|
+
if (!source.includes("import") && !source.includes("require")) return [];
|
|
1622
|
+
const startMs = performance.now();
|
|
1623
|
+
const fileUri = delta.documentUri;
|
|
1624
|
+
const filePath = fileUri.replace(/^file:\/\//, "");
|
|
1625
|
+
this._eventEmitter.emit("scan:start", { fileUri });
|
|
1626
|
+
const langProfile = getLanguageProfile(delta);
|
|
1627
|
+
if (langProfile !== null && langProfile.id !== "javascript") {
|
|
1628
|
+
const durationMs2 = performance.now() - startMs;
|
|
1629
|
+
this._recordScan(0, durationMs2);
|
|
1630
|
+
this._eventEmitter.emit("scan:complete", { fileUri, findingsCount: 0, durationMs: durationMs2 });
|
|
1631
|
+
return [];
|
|
1632
|
+
}
|
|
1633
|
+
const imports = this._extractImports(delta, source);
|
|
1634
|
+
const findings = [];
|
|
1635
|
+
const localDeps = this._loadFileDeps(filePath);
|
|
1636
|
+
for (const imp of imports) {
|
|
1637
|
+
if (signal?.aborted) break;
|
|
1638
|
+
const pkg = imp.packageName;
|
|
1639
|
+
if (PROVIDED_MODULES.has(pkg)) continue;
|
|
1640
|
+
if (pkg.startsWith("@types/")) continue;
|
|
1641
|
+
if (this._config.additionalSafePackages.includes(pkg)) continue;
|
|
1642
|
+
if (this._isAlias(pkg)) continue;
|
|
1643
|
+
const inPackageJson = localDeps.has(pkg) || (this._workspace?.allDeps.has(pkg) ?? false);
|
|
1644
|
+
const inWorkspace = this._workspace?.allPackageNames.has(pkg) ?? false;
|
|
1645
|
+
const inLockfile = this._workspace?.lockfile?.packages.has(pkg) ?? false;
|
|
1646
|
+
const inNodeModules = this._checkNodeModules(pkg);
|
|
1647
|
+
if (inPackageJson || inWorkspace) continue;
|
|
1648
|
+
if (inLockfile && inNodeModules) continue;
|
|
1649
|
+
const inKnownFakeList = KNOWN_FAKE_PACKAGES.has(pkg);
|
|
1650
|
+
const inMaliciousList = KNOWN_MALICIOUS_PACKAGES.has(pkg);
|
|
1651
|
+
const registryResult = this._registry.check(pkg);
|
|
1652
|
+
const registryExists = registryResult?.exists ?? null;
|
|
1653
|
+
const typosquatMatch = this._config.enableTyposquatDetection ? findTyposquatMatch(pkg, this._config.typosquatMaxDistance) : null;
|
|
1654
|
+
const scoringCtx = {
|
|
1655
|
+
inKnownFakeList,
|
|
1656
|
+
inMaliciousList,
|
|
1657
|
+
inPackageJson,
|
|
1658
|
+
inNodeModules,
|
|
1659
|
+
inLockfile,
|
|
1660
|
+
registryExists,
|
|
1661
|
+
typosquatMatch,
|
|
1662
|
+
isScopedPackage: pkg.startsWith("@"),
|
|
1663
|
+
hasPrivateScope: false
|
|
1664
|
+
// TODO: detect from .npmrc / org config
|
|
1665
|
+
};
|
|
1666
|
+
const { score: confidence, signals } = computeConfidence(scoringCtx);
|
|
1667
|
+
if (confidence < this._config.confidenceThreshold) continue;
|
|
1668
|
+
const category = classifyCategory(scoringCtx);
|
|
1669
|
+
const severity = classifySeverity(category, confidence);
|
|
1670
|
+
const finding = {
|
|
1671
|
+
id: deterministicId(fileUri, imp.line, imp.column, category, pkg),
|
|
1672
|
+
engine: "phantom_dep",
|
|
1673
|
+
severity,
|
|
1674
|
+
category,
|
|
1675
|
+
file: fileUri,
|
|
1676
|
+
line: imp.line,
|
|
1677
|
+
column: imp.column,
|
|
1678
|
+
message: messageFor(category, pkg, typosquatMatch ?? void 0),
|
|
1679
|
+
evidence: imp.code,
|
|
1680
|
+
suggestion: suggestionFor(category, pkg, typosquatMatch ?? void 0),
|
|
1681
|
+
confidence,
|
|
1682
|
+
autoFixable: category === "typosquat" || category === "undeclared_dep",
|
|
1683
|
+
ruleId: ruleIdFor(category),
|
|
1684
|
+
signals,
|
|
1685
|
+
similarTo: typosquatMatch?.realPackage,
|
|
1686
|
+
editDistance: typosquatMatch?.distance
|
|
1687
|
+
};
|
|
1688
|
+
findings.push(finding);
|
|
1689
|
+
this._eventEmitter.emit("finding", finding);
|
|
1690
|
+
}
|
|
1691
|
+
const durationMs = performance.now() - startMs;
|
|
1692
|
+
this._recordScan(findings.length, durationMs);
|
|
1693
|
+
this._eventEmitter.emit("scan:complete", { fileUri, findingsCount: findings.length, durationMs });
|
|
1694
|
+
return findings;
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Deep async scan — waits for all registry checks to resolve.
|
|
1698
|
+
* Use for CI/CD pipelines where latency is acceptable.
|
|
1699
|
+
*/
|
|
1700
|
+
async deepScan(delta, signal) {
|
|
1701
|
+
if (signal?.aborted) return [];
|
|
1702
|
+
const startMs = performance.now();
|
|
1703
|
+
const fileUri = delta.documentUri;
|
|
1704
|
+
const filePath = fileUri.replace(/^file:\/\//, "");
|
|
1705
|
+
this._eventEmitter.emit("scan:start", { fileUri });
|
|
1706
|
+
const langProfile = getLanguageProfile(delta);
|
|
1707
|
+
if (langProfile !== null && langProfile.id !== "javascript") {
|
|
1708
|
+
const durationMs2 = performance.now() - startMs;
|
|
1709
|
+
this._recordScan(0, durationMs2);
|
|
1710
|
+
this._eventEmitter.emit("scan:complete", { fileUri, findingsCount: 0, durationMs: durationMs2 });
|
|
1711
|
+
return [];
|
|
1712
|
+
}
|
|
1713
|
+
const imports = this._extractImports(delta, delta.fullText);
|
|
1714
|
+
const findings = [];
|
|
1715
|
+
const localDeps = this._loadFileDeps(filePath);
|
|
1716
|
+
const toCheck = [];
|
|
1717
|
+
for (const imp of imports) {
|
|
1718
|
+
const pkg = imp.packageName;
|
|
1719
|
+
if (PROVIDED_MODULES.has(pkg) || pkg.startsWith("@types/")) continue;
|
|
1720
|
+
if (this._config.additionalSafePackages.includes(pkg)) continue;
|
|
1721
|
+
if (this._isAlias(pkg)) continue;
|
|
1722
|
+
const inPackageJson = localDeps.has(pkg) || (this._workspace?.allDeps.has(pkg) ?? false);
|
|
1723
|
+
const inWorkspace = this._workspace?.allPackageNames.has(pkg) ?? false;
|
|
1724
|
+
if (inPackageJson || inWorkspace) continue;
|
|
1725
|
+
toCheck.push(imp);
|
|
1726
|
+
}
|
|
1727
|
+
const registryResults = await this._registry.batchCheck(
|
|
1728
|
+
toCheck.map((i) => i.packageName)
|
|
1729
|
+
);
|
|
1730
|
+
for (const imp of toCheck) {
|
|
1731
|
+
if (signal?.aborted) break;
|
|
1732
|
+
const pkg = imp.packageName;
|
|
1733
|
+
const inNodeModules = this._checkNodeModules(pkg);
|
|
1734
|
+
const inLockfile = this._workspace?.lockfile?.packages.has(pkg) ?? false;
|
|
1735
|
+
const registryExists = registryResults.get(pkg) ?? null;
|
|
1736
|
+
const inKnownFakeList = KNOWN_FAKE_PACKAGES.has(pkg);
|
|
1737
|
+
const inMaliciousList = KNOWN_MALICIOUS_PACKAGES.has(pkg);
|
|
1738
|
+
const typosquatMatch = this._config.enableTyposquatDetection ? findTyposquatMatch(pkg, this._config.typosquatMaxDistance) : null;
|
|
1739
|
+
const scoringCtx = {
|
|
1740
|
+
inKnownFakeList,
|
|
1741
|
+
inMaliciousList,
|
|
1742
|
+
inPackageJson: false,
|
|
1743
|
+
inNodeModules,
|
|
1744
|
+
inLockfile,
|
|
1745
|
+
registryExists,
|
|
1746
|
+
typosquatMatch,
|
|
1747
|
+
isScopedPackage: pkg.startsWith("@"),
|
|
1748
|
+
hasPrivateScope: false
|
|
1749
|
+
};
|
|
1750
|
+
const { score: confidence, signals } = computeConfidence(scoringCtx);
|
|
1751
|
+
if (confidence < this._config.confidenceThreshold) continue;
|
|
1752
|
+
const category = classifyCategory(scoringCtx);
|
|
1753
|
+
const severity = classifySeverity(category, confidence);
|
|
1754
|
+
const finding = {
|
|
1755
|
+
id: deterministicId(fileUri, imp.line, imp.column, category, pkg),
|
|
1756
|
+
engine: "phantom_dep",
|
|
1757
|
+
severity,
|
|
1758
|
+
category,
|
|
1759
|
+
file: fileUri,
|
|
1760
|
+
line: imp.line,
|
|
1761
|
+
column: imp.column,
|
|
1762
|
+
message: messageFor(category, pkg, typosquatMatch ?? void 0),
|
|
1763
|
+
evidence: imp.code,
|
|
1764
|
+
suggestion: suggestionFor(category, pkg, typosquatMatch ?? void 0),
|
|
1765
|
+
confidence,
|
|
1766
|
+
autoFixable: category === "typosquat" || category === "undeclared_dep",
|
|
1767
|
+
ruleId: ruleIdFor(category),
|
|
1768
|
+
signals,
|
|
1769
|
+
similarTo: typosquatMatch?.realPackage,
|
|
1770
|
+
editDistance: typosquatMatch?.distance
|
|
1771
|
+
};
|
|
1772
|
+
findings.push(finding);
|
|
1773
|
+
this._eventEmitter.emit("finding", finding);
|
|
1774
|
+
}
|
|
1775
|
+
const durationMs = performance.now() - startMs;
|
|
1776
|
+
this._recordScan(findings.length, durationMs);
|
|
1777
|
+
this._eventEmitter.emit("scan:complete", { fileUri, findingsCount: findings.length, durationMs });
|
|
1778
|
+
return findings;
|
|
1779
|
+
}
|
|
1780
|
+
/** Refresh all caches and re-read workspace info */
|
|
1781
|
+
refresh() {
|
|
1782
|
+
this._fsCache.clear();
|
|
1783
|
+
this._loadWorkspace();
|
|
1784
|
+
this._eventEmitter.emit("cache:invalidate", { reason: "manual refresh" });
|
|
1785
|
+
}
|
|
1786
|
+
/** Update engine configuration at runtime */
|
|
1787
|
+
updateConfig(config) {
|
|
1788
|
+
this._config = { ...this._config, ...config };
|
|
1789
|
+
if (config.registryConcurrency || config.registryTimeoutMs || config.customRegistries) {
|
|
1790
|
+
this._registry = new RegistryClient(this._config);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
/** Get engine performance stats */
|
|
1794
|
+
getStats() {
|
|
1795
|
+
return {
|
|
1796
|
+
...this._stats,
|
|
1797
|
+
cacheHitRate: this._cacheHits + this._cacheMisses > 0 ? this._cacheHits / (this._cacheHits + this._cacheMisses) : 0,
|
|
1798
|
+
avgScanMs: this._scanTimesMs.length > 0 ? this._scanTimesMs.reduce((a, b) => a + b, 0) / this._scanTimesMs.length : 0,
|
|
1799
|
+
registryCacheSize: this._registry.cacheSize
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
/** Cleanup watchers and caches */
|
|
1803
|
+
async prepareDispose() {
|
|
1804
|
+
await this._registry.waitForIdle();
|
|
1805
|
+
this._registry.flushPersist();
|
|
1806
|
+
}
|
|
1807
|
+
dispose() {
|
|
1808
|
+
this._registry.flushPersist();
|
|
1809
|
+
for (const file of this._watchedFiles) {
|
|
1810
|
+
try {
|
|
1811
|
+
unwatchFile(file);
|
|
1812
|
+
} catch {
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
this._watchedFiles.clear();
|
|
1816
|
+
this._fsCache.clear();
|
|
1817
|
+
this._registry.clearCache();
|
|
1818
|
+
this._eventEmitter.removeAllListeners();
|
|
1819
|
+
}
|
|
1820
|
+
// ── Private ──
|
|
1821
|
+
_loadWorkspace() {
|
|
1822
|
+
this._workspace = loadWorkspaceInfo(this._workspaceRoot);
|
|
1823
|
+
if (this._config.enableFileWatchers) {
|
|
1824
|
+
this._setupFileWatchers();
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
_setupFileWatchers() {
|
|
1828
|
+
const rootPkg = join(this._workspaceRoot, "package.json");
|
|
1829
|
+
if (existsSync(rootPkg)) {
|
|
1830
|
+
this._watch(rootPkg);
|
|
1831
|
+
}
|
|
1832
|
+
if (this._workspace?.lockfile) {
|
|
1833
|
+
this._watch(this._workspace.lockfile.path);
|
|
1834
|
+
}
|
|
1835
|
+
for (const dir of this._workspace?.packageDirs ?? []) {
|
|
1836
|
+
const pkgPath = join(dir, "package.json");
|
|
1837
|
+
if (existsSync(pkgPath)) {
|
|
1838
|
+
this._watch(pkgPath);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
_watch(filePath) {
|
|
1843
|
+
if (this._watchedFiles.has(filePath)) return;
|
|
1844
|
+
this._watchedFiles.add(filePath);
|
|
1845
|
+
watchFile(filePath, { interval: 5e3 }, () => {
|
|
1846
|
+
this._fsCache.clear();
|
|
1847
|
+
this._loadWorkspace();
|
|
1848
|
+
this._emit({ type: "cache:invalidate", reason: `${filePath} changed` });
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
_loadFileDeps(filePath) {
|
|
1852
|
+
let dir = dirname(filePath);
|
|
1853
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1854
|
+
const pkgPath = join(dir, "package.json");
|
|
1855
|
+
const cacheKey = `deps:${pkgPath}`;
|
|
1856
|
+
const cached = this._fsCache.get(cacheKey);
|
|
1857
|
+
if (cached) {
|
|
1858
|
+
this._cacheHits++;
|
|
1859
|
+
return cached;
|
|
1860
|
+
}
|
|
1861
|
+
if (existsSync(pkgPath)) {
|
|
1862
|
+
this._cacheMisses++;
|
|
1863
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1864
|
+
try {
|
|
1865
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1866
|
+
collectDeps(pkg, deps, /* @__PURE__ */ new Set());
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
if (process.env.VIBECHECK_DEBUG) {
|
|
1869
|
+
console.warn(`[phantom_dep] readFileSync package.json failed for ${pkgPath}:`, err instanceof Error ? err.message : err);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
this._fsCache.set(cacheKey, deps);
|
|
1873
|
+
return deps;
|
|
1874
|
+
}
|
|
1875
|
+
const parent = dirname(dir);
|
|
1876
|
+
if (parent === dir) break;
|
|
1877
|
+
dir = parent;
|
|
1878
|
+
}
|
|
1879
|
+
return /* @__PURE__ */ new Set();
|
|
1880
|
+
}
|
|
1881
|
+
_checkNodeModules(pkg) {
|
|
1882
|
+
const cacheKey = `nm:${pkg}`;
|
|
1883
|
+
const cached = this._fsCache.get(cacheKey);
|
|
1884
|
+
if (cached !== void 0) {
|
|
1885
|
+
this._cacheHits++;
|
|
1886
|
+
return cached.size > 0;
|
|
1887
|
+
}
|
|
1888
|
+
this._cacheMisses++;
|
|
1889
|
+
let dir = this._workspaceRoot;
|
|
1890
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
1891
|
+
if (existsSync(join(dir, "node_modules", pkg))) {
|
|
1892
|
+
this._fsCache.set(cacheKey, /* @__PURE__ */ new Set(["found"]));
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
const parent = dirname(dir);
|
|
1896
|
+
if (parent === dir) break;
|
|
1897
|
+
dir = parent;
|
|
1898
|
+
}
|
|
1899
|
+
this._fsCache.set(cacheKey, /* @__PURE__ */ new Set());
|
|
1900
|
+
return false;
|
|
1901
|
+
}
|
|
1902
|
+
_isAlias(pkg) {
|
|
1903
|
+
if (!this._workspace) return false;
|
|
1904
|
+
for (const alias of this._workspace.tsconfigAliases) {
|
|
1905
|
+
if (pkg === alias || pkg.startsWith(alias + "/") || pkg.startsWith(alias)) return true;
|
|
1906
|
+
}
|
|
1907
|
+
for (const imp of this._workspace.packageJsonImports) {
|
|
1908
|
+
if (pkg === imp || pkg.startsWith(imp + "/")) return true;
|
|
1909
|
+
}
|
|
1910
|
+
if (this._workspace.workspaceProtocol.has(pkg)) return true;
|
|
1911
|
+
if (this._workspace.allPackageNames.has(pkg)) return true;
|
|
1912
|
+
const rootSegment = pkg.includes("/") && !pkg.startsWith("@") ? pkg.split("/")[0] : pkg;
|
|
1913
|
+
if (MONOREPO_DIR_ALIASES.has(rootSegment)) {
|
|
1914
|
+
try {
|
|
1915
|
+
const dirPath = join(this._workspaceRoot, rootSegment);
|
|
1916
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) return true;
|
|
1917
|
+
} catch {
|
|
1918
|
+
}
|
|
1919
|
+
if (this._workspace) {
|
|
1920
|
+
for (const pkgDir of this._workspace.packageDirs) {
|
|
1921
|
+
try {
|
|
1922
|
+
const subDirPath = join(pkgDir, rootSegment);
|
|
1923
|
+
if (existsSync(subDirPath) && statSync(subDirPath).isDirectory()) return true;
|
|
1924
|
+
} catch {
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (pkg.startsWith("@")) {
|
|
1930
|
+
const scope = pkg.split("/")[0];
|
|
1931
|
+
for (const wsName of this._workspace.allPackageNames) {
|
|
1932
|
+
if (wsName.startsWith(scope + "/")) return true;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
_emit(event) {
|
|
1938
|
+
this._eventEmitter.emit(event.type, event);
|
|
1939
|
+
}
|
|
1940
|
+
_recordScan(findings, durationMs) {
|
|
1941
|
+
this._stats.scansCompleted++;
|
|
1942
|
+
this._stats.findingsTotal += findings;
|
|
1943
|
+
this._scanTimesMs.push(durationMs);
|
|
1944
|
+
if (this._scanTimesMs.length > 100) {
|
|
1945
|
+
this._scanTimesMs = this._scanTimesMs.slice(-100);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
var _internals = {
|
|
1950
|
+
extractImports: extractJavaScriptNpmImports,
|
|
1951
|
+
extractPackageName,
|
|
1952
|
+
isRelativeOrAlias,
|
|
1953
|
+
isNpmLikePackageName,
|
|
1954
|
+
damerauLevenshtein,
|
|
1955
|
+
findTyposquatMatch,
|
|
1956
|
+
isVisuallyConfusable,
|
|
1957
|
+
computeConfidence,
|
|
1958
|
+
classifyCategory,
|
|
1959
|
+
parseLockfilePackages,
|
|
1960
|
+
loadWorkspaceInfo,
|
|
1961
|
+
collectPackageJsonImports,
|
|
1962
|
+
KNOWN_FAKE_PACKAGES,
|
|
1963
|
+
KNOWN_MALICIOUS_PACKAGES,
|
|
1964
|
+
POPULAR_PACKAGES,
|
|
1965
|
+
MONOREPO_DIR_ALIASES
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
export { EngineError, EngineErrorCode, PhantomDepEngine, _internals, createContext, extractPythonPypiImports, getLanguageProfile, goLanguageProfile, isEngineError, javascriptLanguageProfile, pythonLanguageProfile, withEngineErrorHandling };
|