@deplens/mcp 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1145 @@
1
+ // inspect.mjs
2
+ import { createRequire } from "module"
3
+ import fs from "fs"
4
+ import path from "path"
5
+ import { fileURLToPath, pathToFileURL } from "url"
6
+ import fg from "fast-glob"
7
+ import { resolve as importMetaResolve } from "import-meta-resolve"
8
+ import { parseDtsFile } from "./parse-dts.mjs"
9
+
10
+ function getPackageName(target) {
11
+ if (!target) return target
12
+ if (target.startsWith("@")) {
13
+ const parts = target.split("/")
14
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : target
15
+ }
16
+ return target.split("/")[0]
17
+ }
18
+
19
+ function getPackageSubpath(target) {
20
+ if (!target) return null
21
+ const base = getPackageName(target)
22
+ if (!base) return null
23
+ if (target === base) return null
24
+ const prefix = `${base}/`
25
+ return target.startsWith(prefix) ? target.slice(prefix.length) : null
26
+ }
27
+
28
+ async function findWorkspaceRoot(startDir) {
29
+ if (!startDir) return null
30
+ let dir = path.resolve(startDir)
31
+ while (true) {
32
+ const pkgPath = path.join(dir, "package.json")
33
+ if (fs.existsSync(pkgPath)) {
34
+ try {
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
36
+ if (pkg?.workspaces) return { dir, pkg }
37
+ } catch (e) {}
38
+ }
39
+ const parent = path.dirname(dir)
40
+ if (parent === dir) break
41
+ dir = parent
42
+ }
43
+ return null
44
+ }
45
+
46
+ async function listWorkspacePackageDirs(rootDir, workspaces, targetPackage) {
47
+ const patterns = Array.isArray(workspaces) ? workspaces : workspaces?.packages
48
+ if (!Array.isArray(patterns) || patterns.length === 0) return []
49
+ const dirs = []
50
+ const target = getPackageName(targetPackage)
51
+
52
+ for (const pattern of patterns) {
53
+ if (!pattern) continue
54
+ const normalized = String(pattern).replace(/\\/g, "/").replace(/\/?$/, "/")
55
+ const globPattern = `${normalized}package.json`
56
+ const matches =
57
+ typeof Bun !== "undefined" && Bun.Glob
58
+ ? await Array.fromAsync(
59
+ new Bun.Glob(globPattern).scan({
60
+ cwd: rootDir,
61
+ absolute: false,
62
+ onlyFiles: true,
63
+ followSymlinks: false,
64
+ dot: false,
65
+ }),
66
+ )
67
+ : await fg(globPattern, {
68
+ cwd: rootDir,
69
+ onlyFiles: true,
70
+ dot: false,
71
+ followSymbolicLinks: false,
72
+ })
73
+ for (const match of matches) {
74
+ const pkgPath = path.join(rootDir, match)
75
+ try {
76
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
77
+ const depTables = [
78
+ pkg?.dependencies,
79
+ pkg?.devDependencies,
80
+ pkg?.peerDependencies,
81
+ pkg?.optionalDependencies,
82
+ ]
83
+ const hasTarget =
84
+ Boolean(target) &&
85
+ depTables.some((deps) => deps && Object.prototype.hasOwnProperty.call(deps, target))
86
+ if (hasTarget) {
87
+ dirs.push(path.dirname(pkgPath))
88
+ }
89
+ } catch (e) {}
90
+ }
91
+ }
92
+
93
+ return dirs
94
+ }
95
+
96
+ async function resolveTargetModule(target, cwd, resolveFrom) {
97
+ const baseDir = resolveFrom || cwd
98
+ if (!baseDir) return { resolved: null, resolveCwd: baseDir, resolver: null }
99
+
100
+ const tryResolve = async (dir) => {
101
+ if (!dir) return null
102
+ if (typeof Bun !== "undefined" && Bun.resolve) {
103
+ try {
104
+ const resolved = await Bun.resolve(target, dir)
105
+ return { resolved, resolver: "bun", resolveCwd: dir }
106
+ } catch (e) {}
107
+ }
108
+ try {
109
+ const parentUrl = pathToFileURL(path.join(dir, "noop.js")).href
110
+ const resolvedUrl = await importMetaResolve(target, parentUrl)
111
+ const resolvedPath = resolvedUrl.startsWith("file://") ? fileURLToPath(resolvedUrl) : resolvedUrl
112
+ return { resolved: resolvedPath, resolver: "import-meta-resolve", resolveCwd: dir }
113
+ } catch (e) {}
114
+ try {
115
+ const req = createRequire(path.join(dir, "noop.js"))
116
+ const resolved = req.resolve(target)
117
+ return { resolved, resolver: "require", resolveCwd: dir }
118
+ } catch (e) {
119
+ return null
120
+ }
121
+ }
122
+
123
+ const direct = await tryResolve(baseDir)
124
+ if (direct) return direct
125
+
126
+ const workspace = await findWorkspaceRoot(baseDir)
127
+ if (workspace?.pkg?.workspaces) {
128
+ const dirs = await listWorkspacePackageDirs(workspace.dir, workspace.pkg.workspaces, target)
129
+ for (const dir of dirs) {
130
+ const resolved = await tryResolve(dir)
131
+ if (resolved) return resolved
132
+ }
133
+ }
134
+
135
+ return { resolved: null, resolveCwd: baseDir, resolver: null }
136
+ }
137
+
138
+ function findPackageJsonFromPath(startPath) {
139
+ if (!startPath) return null
140
+ let dir = path.dirname(startPath)
141
+ for (let i = 0; i < 10; i++) {
142
+ const candidate = path.join(dir, "package.json")
143
+ if (fs.existsSync(candidate)) return candidate
144
+ const parent = path.dirname(dir)
145
+ if (parent === dir) break
146
+ dir = parent
147
+ }
148
+ return null
149
+ }
150
+
151
+ function findPackageJsonInNodeModules(startDir, basePkg) {
152
+ if (!startDir || !basePkg) return null
153
+ const segments = basePkg.split("/")
154
+ let dir = path.resolve(startDir)
155
+ while (true) {
156
+ const candidate = path.join(dir, "node_modules", ...segments, "package.json")
157
+ if (fs.existsSync(candidate)) return candidate
158
+ const parent = path.dirname(dir)
159
+ if (parent === dir) break
160
+ dir = parent
161
+ }
162
+ return null
163
+ }
164
+
165
+ function resolvePackageInfo(basePkg, require, resolveFrom, resolvedPath) {
166
+ let pkgPath
167
+ let pkgDir
168
+ try {
169
+ pkgPath = require.resolve(`${basePkg}/package.json`)
170
+ pkgDir = path.dirname(pkgPath)
171
+ } catch (e) {
172
+ try {
173
+ const mainPath = require.resolve(basePkg)
174
+ let dir = path.dirname(mainPath)
175
+ for (let i = 0; i < 5; i++) {
176
+ const candidate = path.join(dir, "package.json")
177
+ if (fs.existsSync(candidate)) {
178
+ pkgPath = candidate
179
+ pkgDir = dir
180
+ break
181
+ }
182
+ dir = path.dirname(dir)
183
+ }
184
+ } catch (err) {}
185
+ }
186
+
187
+ if (!pkgPath && resolveFrom && basePkg) {
188
+ const fallback = findPackageJsonInNodeModules(resolveFrom, basePkg)
189
+ if (fallback) {
190
+ pkgPath = fallback
191
+ pkgDir = path.dirname(fallback)
192
+ }
193
+ }
194
+
195
+ if (!pkgPath && resolvedPath) {
196
+ const fallback = findPackageJsonFromPath(resolvedPath)
197
+ if (fallback) {
198
+ pkgPath = fallback
199
+ pkgDir = path.dirname(fallback)
200
+ }
201
+ }
202
+
203
+ if (resolveFrom && basePkg) {
204
+ const rootCandidate = findPackageJsonInNodeModules(resolveFrom, basePkg)
205
+ if (rootCandidate && rootCandidate !== pkgPath) {
206
+ pkgPath = rootCandidate
207
+ pkgDir = path.dirname(rootCandidate)
208
+ }
209
+ }
210
+
211
+ if (!pkgPath || !fs.existsSync(pkgPath)) return null
212
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
213
+ return { pkg, pkgPath, pkgDir }
214
+ }
215
+
216
+ function resolveTypesFromExportEntry(entry) {
217
+ if (!entry) return null
218
+ if (typeof entry === "string") return entry
219
+ if (typeof entry === "object") {
220
+ if (typeof entry.types === "string") return entry.types
221
+ for (const key of ["import", "require", "default"]) {
222
+ if (typeof entry[key] === "string") return entry[key]
223
+ }
224
+ }
225
+ return null
226
+ }
227
+
228
+ function coerceTypesPath(typesPath) {
229
+ if (!typesPath) return typesPath
230
+ if (typesPath.endsWith(".d.ts") || typesPath.endsWith(".d.cts") || typesPath.endsWith(".d.mts")) {
231
+ return typesPath
232
+ }
233
+ if (typesPath.endsWith(".mjs")) return typesPath.replace(/\.mjs$/, ".d.mts")
234
+ if (typesPath.endsWith(".cjs")) return typesPath.replace(/\.cjs$/, ".d.cts")
235
+ if (typesPath.endsWith(".js")) return typesPath.replace(/\.js$/, ".d.ts")
236
+ return typesPath
237
+ }
238
+
239
+ function resolveTypesFile(pkg, pkgDir, subpath) {
240
+ if (!pkg || !pkgDir) return { typesFile: null, dtsPath: null, source: null }
241
+ let typesFile = null
242
+ let source = null
243
+
244
+ if (pkg.exports) {
245
+ let entry = null
246
+ if (subpath) {
247
+ const key = subpath.startsWith(".") ? subpath : `./${subpath}`
248
+ if (typeof pkg.exports === "object") {
249
+ entry = pkg.exports[key]
250
+ }
251
+ } else if (typeof pkg.exports === "string") {
252
+ entry = pkg.exports
253
+ } else if (typeof pkg.exports === "object") {
254
+ entry = pkg.exports["."] ?? pkg.exports["./"]
255
+ }
256
+
257
+ const typesFromExport = resolveTypesFromExportEntry(entry)
258
+ if (typesFromExport) {
259
+ typesFile = coerceTypesPath(typesFromExport)
260
+ source = "exports"
261
+ }
262
+ }
263
+
264
+ if (!typesFile) {
265
+ typesFile = pkg.types || pkg.typings || null
266
+ if (typesFile) source = "package"
267
+ }
268
+
269
+ if (!typesFile) {
270
+ const candidates = [
271
+ "dist/index.d.ts",
272
+ "dist/index.d.cts",
273
+ "dist/index.d.mts",
274
+ "lib/index.d.ts",
275
+ "lib/index.d.cts",
276
+ "lib/index.d.mts",
277
+ "index.d.ts",
278
+ "index.d.cts",
279
+ "index.d.mts",
280
+ "types/index.d.ts",
281
+ ]
282
+ for (const candidate of candidates) {
283
+ const candidatePath = path.resolve(pkgDir, candidate)
284
+ if (fs.existsSync(candidatePath)) {
285
+ typesFile = candidate
286
+ source = "fallback"
287
+ break
288
+ }
289
+ }
290
+ }
291
+
292
+ if (!typesFile) {
293
+ return { typesFile: null, dtsPath: null, source: null }
294
+ }
295
+
296
+ const resolved = path.isAbsolute(typesFile) ? typesFile : path.resolve(pkgDir, typesFile)
297
+ let dtsPath = resolved
298
+ if (!fs.existsSync(dtsPath)) {
299
+ const mapped = coerceTypesPath(dtsPath)
300
+ if (mapped !== dtsPath && fs.existsSync(mapped)) {
301
+ dtsPath = mapped
302
+ } else {
303
+ const replacements = [".d.ts", ".d.cts", ".d.mts"]
304
+ for (const ext of replacements) {
305
+ if (dtsPath.endsWith(ext)) continue
306
+ const candidate = `${dtsPath}${ext}`
307
+ if (fs.existsSync(candidate)) {
308
+ dtsPath = candidate
309
+ break
310
+ }
311
+ }
312
+ if (!fs.existsSync(dtsPath) && dtsPath.endsWith(".d.ts")) {
313
+ const ctsPath = dtsPath.replace(".d.ts", ".d.cts")
314
+ const mtsPath = dtsPath.replace(".d.ts", ".d.mts")
315
+ if (fs.existsSync(ctsPath)) dtsPath = ctsPath
316
+ else if (fs.existsSync(mtsPath)) dtsPath = mtsPath
317
+ }
318
+ }
319
+ }
320
+
321
+ if (!fs.existsSync(dtsPath)) {
322
+ const altDir = path.dirname(dtsPath)
323
+ const altCandidates = [
324
+ "types.d.ts",
325
+ "types.d.mts",
326
+ "types.d.cts",
327
+ "index.d.ts",
328
+ "index.d.mts",
329
+ "index.d.cts",
330
+ ]
331
+ for (const candidate of altCandidates) {
332
+ const altPath = path.join(altDir, candidate)
333
+ if (fs.existsSync(altPath)) {
334
+ dtsPath = altPath
335
+ typesFile = path.relative(pkgDir, altPath)
336
+ if (!source || source === "exports" || source === "package") {
337
+ source = "fallback"
338
+ }
339
+ break
340
+ }
341
+ }
342
+ }
343
+
344
+ return { typesFile, dtsPath, source }
345
+ }
346
+
347
+ function normalizeCjsExports(exportsValue) {
348
+ if (exportsValue === null || exportsValue === undefined) {
349
+ return { default: exportsValue }
350
+ }
351
+ if (typeof exportsValue === "object" || typeof exportsValue === "function") {
352
+ return { ...exportsValue, default: exportsValue }
353
+ }
354
+ return { default: exportsValue }
355
+ }
356
+
357
+ function detectModuleFormat(resolvedPath, pkg) {
358
+ const ext = path.extname(resolvedPath)
359
+ if (ext === ".cjs" || ext === ".cts") return "cjs"
360
+ if (ext === ".mjs" || ext === ".mts") return "esm"
361
+ if (pkg?.type === "module") return "esm"
362
+ return "cjs"
363
+ }
364
+
365
+ function isProbablyClass(fn) {
366
+ if (typeof fn !== "function") return false
367
+ const source = Function.prototype.toString.call(fn)
368
+ if (source.startsWith("class ")) return true
369
+ if (fn.prototype) {
370
+ const protoProps = Object.getOwnPropertyNames(fn.prototype).filter((p) => p !== "constructor")
371
+ if (protoProps.length > 0) return true
372
+ }
373
+ return false
374
+ }
375
+
376
+ async function loadModuleExports(resolvedPath, require, pkg) {
377
+ const format = detectModuleFormat(resolvedPath, pkg)
378
+ if (format === "cjs") {
379
+ const mod = require(resolvedPath)
380
+ return { module: normalizeCjsExports(mod), format }
381
+ }
382
+ const mod = await import(pathToFileURL(resolvedPath).href)
383
+ return { module: mod, format }
384
+ }
385
+
386
+ function buildSymbolMatcher(symbols, fallbackFilter) {
387
+ const patterns = []
388
+ const addPattern = (value) => {
389
+ if (!value) return
390
+ if (value.startsWith("/") && value.endsWith("/") && value.length > 2) {
391
+ try {
392
+ patterns.push({ type: "regex", value: new RegExp(value.slice(1, -1), "i") })
393
+ return
394
+ } catch (e) {
395
+ patterns.push({ type: "substring", value: value.toLowerCase() })
396
+ return
397
+ }
398
+ }
399
+ if (value.includes("*")) {
400
+ const escaped = value.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")
401
+ patterns.push({ type: "regex", value: new RegExp(`^${escaped}$`, "i") })
402
+ return
403
+ }
404
+ patterns.push({ type: "substring", value: value.toLowerCase() })
405
+ }
406
+
407
+ if (Array.isArray(symbols)) {
408
+ symbols.forEach(addPattern)
409
+ } else if (typeof symbols === "string") {
410
+ addPattern(symbols)
411
+ } else if (fallbackFilter) {
412
+ addPattern(fallbackFilter)
413
+ }
414
+
415
+ if (patterns.length === 0) return null
416
+ return (name) => {
417
+ const lower = name.toLowerCase()
418
+ return patterns.some((pattern) => {
419
+ if (pattern.type === "regex") return pattern.value.test(name)
420
+ return lower.includes(pattern.value)
421
+ })
422
+ }
423
+ }
424
+
425
+ function truncateSummary(text, mode, maxLen, truncateMode) {
426
+ if (!text) return ""
427
+ const normalized = text.replace(/\s+/g, " ").trim()
428
+ if (truncateMode === "none") return normalized
429
+ const limit = typeof maxLen === "number" ? maxLen : mode === "compact" ? 240 : 1200
430
+ if (normalized.length <= limit) return normalized
431
+ if (truncateMode === "sentence") {
432
+ const slice = normalized.slice(0, limit)
433
+ const lastPeriod = Math.max(slice.lastIndexOf("."), slice.lastIndexOf("!"), slice.lastIndexOf("?"))
434
+ if (lastPeriod > 40) {
435
+ return `${slice.slice(0, lastPeriod + 1)}`
436
+ }
437
+ }
438
+ if (truncateMode === "word") {
439
+ const slice = normalized.slice(0, limit)
440
+ const lastSpace = slice.lastIndexOf(" ")
441
+ if (lastSpace > 40) {
442
+ return `${slice.slice(0, lastSpace)}...`
443
+ }
444
+ }
445
+ return `${normalized.slice(0, Math.max(0, limit - 3))}...`
446
+ }
447
+
448
+ function formatJsdocEntry(name, doc, options) {
449
+ const mode = options.mode || "compact"
450
+ const truncateMode = options.truncate || "word"
451
+ const maxLen = options.maxLen
452
+ const sections = options.sections && options.sections.length > 0 ? options.sections : ["summary", "tags"]
453
+ const includeTags = options.tags?.include || null
454
+ const excludeTags = options.tags?.exclude || null
455
+
456
+ const summary = truncateSummary(doc.summary || "", mode, maxLen, truncateMode)
457
+ const tags = doc.tags || {}
458
+ const tagLines = []
459
+
460
+ const addTag = (tagName, values) => {
461
+ if (!values || values.length === 0) {
462
+ tagLines.push(`@${tagName}`)
463
+ return
464
+ }
465
+ for (const value of values) {
466
+ tagLines.push(value ? `@${tagName} ${value}` : `@${tagName}`)
467
+ }
468
+ }
469
+
470
+ const wantParams = sections.includes("params")
471
+ const wantReturns = sections.includes("returns")
472
+ const wantTags = sections.includes("tags")
473
+
474
+ if (wantParams) {
475
+ if (tags.param) addTag("param", tags.param)
476
+ }
477
+ if (wantReturns) {
478
+ if (tags.returns) addTag("returns", tags.returns)
479
+ if (tags.return) addTag("return", tags.return)
480
+ }
481
+ if (wantTags) {
482
+ for (const [tagName, values] of Object.entries(tags)) {
483
+ if (tagName === "param" || tagName === "returns" || tagName === "return") continue
484
+ if (includeTags && !includeTags.includes(tagName)) continue
485
+ if (excludeTags && excludeTags.includes(tagName)) continue
486
+ if (mode === "compact" && !includeTags && !excludeTags) {
487
+ if (!["deprecated", "since", "experimental"].includes(tagName)) continue
488
+ }
489
+ addTag(tagName, values)
490
+ }
491
+ }
492
+
493
+ const parts = []
494
+ if (sections.includes("summary") && summary) {
495
+ parts.push(summary)
496
+ }
497
+ if (tagLines.length > 0) {
498
+ parts.push(tagLines.join("; "))
499
+ }
500
+
501
+ return `${name}: ${parts.join(" | ")}`.trim()
502
+ }
503
+
504
+ function filterTypeInfo(typeInfo, filter, kindFilter) {
505
+ if (!typeInfo) return null
506
+ const includeName = (name) => !filter || name.toLowerCase().includes(filter)
507
+ const allow = (kind) => !kindFilter || kindFilter.length === 0 || kindFilter.includes(kind)
508
+ const includeExtras = !kindFilter || kindFilter.length === 0
509
+
510
+ const filtered = {
511
+ functions: {},
512
+ interfaces: {},
513
+ types: {},
514
+ classes: {},
515
+ enums: {},
516
+ namespaces: {},
517
+ defaults: [],
518
+ jsdoc: {},
519
+ }
520
+
521
+ if (allow("function")) {
522
+ for (const [name, info] of Object.entries(typeInfo.functions)) {
523
+ if (includeName(name)) filtered.functions[name] = info
524
+ }
525
+ }
526
+
527
+ if (allow("interface")) {
528
+ for (const [name, props] of Object.entries(typeInfo.interfaces)) {
529
+ if (includeName(name)) filtered.interfaces[name] = props
530
+ }
531
+ }
532
+
533
+ if (allow("type")) {
534
+ for (const [name, def] of Object.entries(typeInfo.types)) {
535
+ if (includeName(name)) filtered.types[name] = def
536
+ }
537
+ }
538
+
539
+ if (allow("class")) {
540
+ for (const [name, info] of Object.entries(typeInfo.classes)) {
541
+ if (includeName(name)) filtered.classes[name] = info
542
+ }
543
+ }
544
+
545
+ if (includeExtras) {
546
+ for (const [name, members] of Object.entries(typeInfo.enums || {})) {
547
+ if (includeName(name)) filtered.enums[name] = members
548
+ }
549
+ for (const [name, value] of Object.entries(typeInfo.namespaces || {})) {
550
+ if (includeName(name)) filtered.namespaces[name] = value
551
+ }
552
+ filtered.defaults = (typeInfo.defaults || []).filter((value) => includeName("default") || includeName(value))
553
+ }
554
+
555
+ if (typeInfo.jsdoc) {
556
+ for (const [name, doc] of Object.entries(typeInfo.jsdoc)) {
557
+ if (includeName(name)) filtered.jsdoc[name] = doc
558
+ }
559
+ }
560
+
561
+ return filtered
562
+ }
563
+
564
+ // Helper function to inspect object properties recursively
565
+ function inspectObject(obj, currentDepth = 0, maxDepth = 1, indent = " ") {
566
+ if (currentDepth >= maxDepth || obj === null || obj === undefined) {
567
+ return []
568
+ }
569
+
570
+ const lines = []
571
+ try {
572
+ const descriptors = Object.getOwnPropertyDescriptors(obj)
573
+ const keys = Object.keys(descriptors).slice(0, 10) // Limit to first 10 properties
574
+ for (const key of keys) {
575
+ try {
576
+ const descriptor = descriptors[key]
577
+ if (descriptor.get || descriptor.set) {
578
+ lines.push(`${indent.repeat(currentDepth + 1)}${key}: <getter>`)
579
+ continue
580
+ }
581
+ const value = descriptor.value
582
+ const type = typeof value
583
+ const prefix = indent.repeat(currentDepth + 1)
584
+
585
+ if (type === "function") {
586
+ const paramCount = value.length
587
+ lines.push(`${prefix}${key}(${paramCount} param${paramCount !== 1 ? "s" : ""})`)
588
+ } else if (type === "object" && value !== null) {
589
+ lines.push(`${prefix}${key}: {object}`)
590
+ if (currentDepth + 1 < maxDepth) {
591
+ lines.push(...inspectObject(value, currentDepth + 1, maxDepth, indent))
592
+ }
593
+ } else {
594
+ const valStr = type === "string" ? `"${String(value).substring(0, 30)}"` : String(value).substring(0, 30)
595
+ lines.push(`${prefix}${key}: ${valStr}`)
596
+ }
597
+ } catch (e) {
598
+ // Skip properties that throw on access
599
+ }
600
+ }
601
+ const totalKeys = Object.keys(descriptors).length
602
+ if (totalKeys > 10) {
603
+ lines.push(`${indent.repeat(currentDepth + 1)}... and ${totalKeys - 10} more`)
604
+ }
605
+ } catch (e) {
606
+ // Skip if object is not enumerable
607
+ }
608
+ return lines
609
+ }
610
+
611
+ export async function runInspect(options) {
612
+ const collect = !options?.write && !options?.writeError
613
+ const output = collect ? [] : null
614
+ const write = options?.write
615
+ const writeError = options?.writeError ?? options?.write
616
+
617
+ const log = (line = "") => {
618
+ if (collect) output.push(String(line))
619
+ else if (write) write(String(line))
620
+ }
621
+ const logErr = (line = "") => {
622
+ if (collect) output.push(String(line))
623
+ else if (writeError) writeError(String(line))
624
+ }
625
+
626
+ const target = options?.target
627
+ const filter = options?.filter ? options.filter.toLowerCase() : null
628
+ const showTypes = Boolean(options?.showTypes)
629
+ const jsdocModeRaw = options?.jsdoc ? String(options.jsdoc).toLowerCase() : null
630
+ const jsdocQuery = options?.jsdocQuery || null
631
+ const jsdocOutputRaw = options?.jsdocOutput ? String(options.jsdocOutput).toLowerCase() : null
632
+ const jsdocOutput = jsdocOutputRaw || (jsdocQuery ? "section" : "off")
633
+ const wantJsdoc = jsdocOutput !== "off"
634
+ const jsdocMode =
635
+ showTypes || wantJsdoc ? (jsdocQuery?.mode || jsdocModeRaw || "compact") : "off"
636
+ const kindFilter = Array.isArray(options?.kind)
637
+ ? options.kind.map((k) => String(k).trim().toLowerCase())
638
+ : null
639
+ let depth = typeof options?.depth === "number" ? options.depth : 1
640
+ if (isNaN(depth) || depth < 0 || depth > 5) depth = 1
641
+
642
+ if (!target) {
643
+ logErr(
644
+ "Uso: node inspect.mjs <pacote> [filtro] [--filter VALUE] [--types] [--jsdoc off|compact|full] [--jsdoc-output off|section|inline|only] [--jsdoc-symbol NAME|glob|/re/] [--jsdoc-sections summary,params,returns,tags] [--jsdoc-tags t1,t2] [--jsdoc-tags-exclude t1,t2] [--jsdoc-truncate none|sentence|word] [--jsdoc-max-len N] [--kind function,class,...] [--depth N] [--resolve-from DIR]",
645
+ )
646
+ return collect ? output.join("\n") : ""
647
+ }
648
+
649
+ const baseCwd = options?.cwd
650
+ const resolveFrom = options?.resolveFrom
651
+ ? path.resolve(baseCwd || process.cwd(), options.resolveFrom)
652
+ : baseCwd
653
+ if (baseCwd) {
654
+ try {
655
+ process.chdir(baseCwd)
656
+ } catch (e) {}
657
+ }
658
+
659
+ const resolution = await resolveTargetModule(target, baseCwd, resolveFrom)
660
+ const resolveCwd = resolution.resolveCwd || baseCwd
661
+ const require = createRequire(resolveCwd ? path.join(resolveCwd, "noop.js") : import.meta.url)
662
+ const resolvedPath = resolution.resolved
663
+ const entrypointPath =
664
+ typeof resolvedPath === "string" && resolvedPath.startsWith("file://")
665
+ ? fileURLToPath(resolvedPath)
666
+ : resolvedPath
667
+ const entrypointExists = entrypointPath ? fs.existsSync(entrypointPath) : false
668
+
669
+ const flags = []
670
+ if (filter) flags.push(`Filtro: "${filter}"`)
671
+ if (kindFilter) flags.push(`Kind: ${kindFilter.join(",")}`)
672
+ if (showTypes || wantJsdoc) flags.push("Type Analysis")
673
+ if (jsdocOutput !== "off") flags.push(`JSDoc: ${jsdocMode}`)
674
+ if (depth > 1) flags.push(`Depth: ${depth}`)
675
+
676
+ const flagsStr = flags.length > 0 ? ` (${flags.join(" | ")})` : ""
677
+ log(`🔍 Target: ${target}${flagsStr}`)
678
+
679
+ try {
680
+ if (!resolution.resolved) {
681
+ logErr(`\n❌ Erro: Não foi possível resolver '${target}'`)
682
+ logErr(`ResolveFrom: ${resolveFrom || baseCwd || "unknown"}`)
683
+ logErr(`Certifique-se que '${target}' está instalado e é um caminho válido.`)
684
+ return collect ? output.join("\n") : ""
685
+ }
686
+
687
+ const basePkg = getPackageName(target)
688
+ const subpath = getPackageSubpath(target)
689
+ const pkgInfo = basePkg
690
+ ? resolvePackageInfo(basePkg, require, resolveFrom || baseCwd || process.cwd(), entrypointPath)
691
+ : null
692
+ const pkg = pkgInfo?.pkg
693
+ const pkgDir = pkgInfo?.pkgDir
694
+
695
+ log(`\n🧭 Resolution:`)
696
+ log(` ResolveFrom: ${resolveFrom || baseCwd || "unknown"}`)
697
+ log(` Entrypoint: ${resolution.resolved}`)
698
+ if (resolution.resolver) {
699
+ log(` Resolver: ${resolution.resolver}`)
700
+ }
701
+ if (pkgDir) {
702
+ log(` PackageRoot: ${pkgDir}`)
703
+ }
704
+
705
+ let dtsPath
706
+ let typesFile
707
+ let typesSource
708
+
709
+ if (pkg) {
710
+ log(`\n📄 Package Info:`)
711
+ log(` Name: ${pkg.name || basePkg}`)
712
+ log(` Version: ${pkg.version || "Unknown"}`)
713
+ if (pkg.description) {
714
+ log(` Description: ${pkg.description}`)
715
+ }
716
+ if (pkg.license) {
717
+ log(` License: ${pkg.license}`)
718
+ }
719
+
720
+ const typesResolution = resolveTypesFile(pkg, pkgDir, subpath)
721
+ typesFile = typesResolution.typesFile
722
+ dtsPath = typesResolution.dtsPath
723
+ typesSource = typesResolution.source
724
+
725
+ if (typesFile) {
726
+ const sourceLabel = typesSource ? ` (${typesSource})` : ""
727
+ const existsLabel = dtsPath && fs.existsSync(dtsPath) ? "" : " (missing)"
728
+ log(` Types: ${typesFile}${sourceLabel}${existsLabel}`)
729
+ } else {
730
+ log(` Types: Not found`)
731
+ }
732
+
733
+ // === Mostrar subpath exports ===
734
+ if (pkg.exports && typeof pkg.exports === "object") {
735
+ const exportEntries = Object.entries(pkg.exports)
736
+ if (exportEntries.length > 0) {
737
+ log(`\n🚪 Subpath Exports (${exportEntries.length} available):`)
738
+ for (const [pathKey, value] of exportEntries.slice(0, 10)) {
739
+ if (typeof value === "string") {
740
+ log(` ${pathKey} → ${value}`)
741
+ } else if (value && typeof value === "object") {
742
+ const targets = Object.keys(value).join(", ")
743
+ log(` ${pathKey} → { ${targets} }`)
744
+ }
745
+ }
746
+ if (exportEntries.length > 10) {
747
+ log(` ... and ${exportEntries.length - 10} more`)
748
+ }
749
+ }
750
+ }
751
+ }
752
+
753
+ let typeInfoRaw = null
754
+ if ((showTypes || wantJsdoc) && dtsPath && fs.existsSync(dtsPath)) {
755
+ typeInfoRaw = parseDtsFile(dtsPath, null)
756
+ }
757
+
758
+ let moduleNamespace = {}
759
+ let moduleDescriptors = {}
760
+ let allExports = []
761
+ const runtimeAvailable = Boolean(entrypointExists)
762
+ if (!runtimeAvailable) {
763
+ log(`\n⚠️ Entrypoint not found on disk; runtime exports skipped.`)
764
+ } else {
765
+ const { module: loadedNamespace } = await loadModuleExports(entrypointPath, require, pkg)
766
+ moduleNamespace = loadedNamespace
767
+ moduleDescriptors = Object.getOwnPropertyDescriptors(moduleNamespace)
768
+ allExports = Object.keys(moduleDescriptors)
769
+ }
770
+
771
+ // Lógica de Filtro
772
+ let finalList = allExports
773
+ if (filter) {
774
+ finalList = allExports.filter((key) => key.toLowerCase().includes(filter))
775
+ }
776
+
777
+ // Se a lista for muito grande e não tiver filtro, avisa e corta
778
+ const LIMIT = 100
779
+ if (!filter && finalList.length > LIMIT) {
780
+ log(`\n⚠️ Módulo exporta ${finalList.length} itens. Mostrando os primeiros ${LIMIT}...`)
781
+ log(`DICA: Use o parâmetro 'filter' para encontrar o que procura.`)
782
+ finalList = finalList.slice(0, LIMIT)
783
+ }
784
+
785
+ // === MELHORIA 2: Categorizar exports por tipo ===
786
+ const categorized = {
787
+ functions: [],
788
+ classes: [],
789
+ objects: [],
790
+ primitives: [],
791
+ constants: [],
792
+ }
793
+
794
+ if (runtimeAvailable) {
795
+ for (const key of finalList) {
796
+ const descriptor = moduleDescriptors[key]
797
+ if (!descriptor) continue
798
+ if (descriptor.get || descriptor.set) {
799
+ categorized.objects.push(key)
800
+ continue
801
+ }
802
+ const value = descriptor.value
803
+ const type = typeof value
804
+
805
+ if (type === "function") {
806
+ // Distinguir class vs function
807
+ if (isProbablyClass(value)) {
808
+ categorized.classes.push(key)
809
+ } else {
810
+ categorized.functions.push(key)
811
+ }
812
+ } else if (type === "object" && value !== null) {
813
+ categorized.objects.push(key)
814
+ } else if (type === "string" || type === "number" || type === "boolean") {
815
+ categorized.constants.push(key)
816
+ } else {
817
+ categorized.primitives.push(key)
818
+ }
819
+ }
820
+
821
+ // Prefer class from type info when available
822
+ if (typeInfoRaw && Object.keys(typeInfoRaw.classes).length > 0) {
823
+ const classNames = new Set(Object.keys(typeInfoRaw.classes))
824
+ categorized.functions = categorized.functions.filter((name) => {
825
+ if (classNames.has(name)) {
826
+ categorized.classes.push(name)
827
+ return false
828
+ }
829
+ return true
830
+ })
831
+ }
832
+
833
+ // Apply kind filter if specified
834
+ if (kindFilter && kindFilter.length > 0) {
835
+ const kindMap = {
836
+ function: "functions",
837
+ class: "classes",
838
+ object: "objects",
839
+ constant: "constants",
840
+ }
841
+
842
+ // Keep only the requested kinds
843
+ for (const [key, value] of Object.entries(categorized)) {
844
+ const shouldKeep = Object.entries(kindMap).some(
845
+ ([kind, catKey]) => kindFilter.includes(kind) && catKey === key,
846
+ )
847
+ if (!shouldKeep) {
848
+ categorized[key] = []
849
+ }
850
+ }
851
+
852
+ // Update finalList to only include filtered kinds
853
+ finalList = [...categorized.functions, ...categorized.classes, ...categorized.objects, ...categorized.constants]
854
+ }
855
+ }
856
+
857
+ // Mostrar exports categorizados
858
+ if (jsdocOutput !== "only") {
859
+ if (!runtimeAvailable) {
860
+ log(`\nℹ️ Runtime exports unavailable. Use --types to inspect type exports.`)
861
+ }
862
+ log(`\n🔑 Exports Encontrados (${finalList.length} total):`)
863
+
864
+ if (categorized.functions.length > 0) {
865
+ log(`\n 📘 Functions (${categorized.functions.length}):`)
866
+ log(` ${categorized.functions.join(", ")}`)
867
+ }
868
+
869
+ if (categorized.classes.length > 0) {
870
+ log(`\n 🏛️ Classes (${categorized.classes.length}):`)
871
+ log(` ${categorized.classes.join(", ")}`)
872
+ }
873
+
874
+ if (categorized.objects.length > 0) {
875
+ log(`\n 📦 Objects/Namespaces (${categorized.objects.length}):`)
876
+ log(` ${categorized.objects.join(", ")}`)
877
+
878
+ // If depth > 0, show object contents
879
+ if (depth > 0 && categorized.objects.length <= 10) {
880
+ log(`\n 📦 Object Contents (depth: ${depth}):`)
881
+ for (const objName of categorized.objects) {
882
+ log(`\n ${objName}:`)
883
+ const descriptor = moduleDescriptors[objName]
884
+ if (!descriptor || descriptor.get || descriptor.set) {
885
+ log(` ${objName}: <getter>`)
886
+ continue
887
+ }
888
+ const objValue = descriptor.value
889
+ const lines = inspectObject(objValue, 0, depth, " ")
890
+ lines.forEach((line) => log(` ${line}`))
891
+ }
892
+ } else if (depth > 0 && categorized.objects.length > 10) {
893
+ log(`\n ℹ️ Too many objects to show contents. Use 'filter' to narrow down.`)
894
+ }
895
+ }
896
+
897
+ if (categorized.constants.length > 0) {
898
+ log(`\n 🔢 Constants (${categorized.constants.length}):`)
899
+ log(` ${categorized.constants.join(", ")}`)
900
+ }
901
+
902
+ if (finalList.length === 0) {
903
+ log("Nenhum export corresponde ao filtro.")
904
+ }
905
+ }
906
+
907
+ // === MELHORIA 5: Mostrar assinaturas de funções ===
908
+ if (jsdocOutput !== "only") {
909
+ if (!runtimeAvailable) {
910
+ // Skip runtime-only signature/default export hints when entrypoint is missing
911
+ } else if (!showTypes && categorized.functions.length > 0 && categorized.functions.length <= 15) {
912
+ log(`\n✍️ Function Signatures:`)
913
+ for (const fname of categorized.functions) {
914
+ const descriptor = moduleDescriptors[fname]
915
+ const fn = descriptor?.value
916
+ if (typeof fn === "function") {
917
+ const paramCount = fn.length
918
+ const params = paramCount === 0 ? "" : paramCount === 1 ? "1 param" : `${paramCount} params`
919
+ log(` ${fname}(${params})`)
920
+ }
921
+ }
922
+ }
923
+
924
+ // Default export handling
925
+ if (runtimeAvailable) {
926
+ const defaultDescriptor = moduleDescriptors.default
927
+ if (defaultDescriptor && (!filter || "default".includes(filter))) {
928
+ const defaultValue = defaultDescriptor.get || defaultDescriptor.set ? undefined : defaultDescriptor.value
929
+ const defaultType = typeof defaultValue
930
+ log(`\n📦 Default Export: ${defaultType}`)
931
+ if (defaultType === "function" && defaultValue && defaultValue.length !== undefined) {
932
+ log(` Parameters: ${defaultValue.length}`)
933
+ }
934
+ }
935
+ }
936
+ }
937
+
938
+ // === NEW: Parse .d.ts file if --types flag is present ===
939
+ if (showTypes || wantJsdoc) {
940
+ if (dtsPath && fs.existsSync(dtsPath)) {
941
+ if (jsdocOutput !== "only") {
942
+ log(`\n🔬 Type Definitions Analysis:`)
943
+ log(` Source: ${path.basename(dtsPath)}`)
944
+ }
945
+
946
+ const typeInfo = filterTypeInfo(typeInfoRaw, filter, kindFilter)
947
+
948
+ if (typeInfo) {
949
+ if (jsdocOutput !== "only") {
950
+ // Show function signatures with full type info
951
+ if (Object.keys(typeInfo.functions).length > 0) {
952
+ log(`\n 📘 Function Type Signatures:`)
953
+ for (const [name, info] of Object.entries(typeInfo.functions)) {
954
+ log(` ${name}(${info.params}): ${info.returnType}`)
955
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
956
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
957
+ mode: jsdocMode,
958
+ truncate: jsdocQuery?.truncate,
959
+ maxLen: jsdocQuery?.maxLen,
960
+ sections: jsdocQuery?.sections,
961
+ tags: jsdocQuery?.tags,
962
+ })
963
+ log(` ↳ ${entry}`)
964
+ }
965
+ }
966
+ }
967
+
968
+ // Show interfaces
969
+ if (Object.keys(typeInfo.interfaces).length > 0) {
970
+ log(`\n 📋 Interfaces:`)
971
+ for (const [name, props] of Object.entries(typeInfo.interfaces)) {
972
+ log(` interface ${name} {`)
973
+ props.forEach((prop) => log(` ${prop}`))
974
+ if (props.length === 5) {
975
+ log(` ... (truncated)`)
976
+ }
977
+ log(` }`)
978
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
979
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
980
+ mode: jsdocMode,
981
+ truncate: jsdocQuery?.truncate,
982
+ maxLen: jsdocQuery?.maxLen,
983
+ sections: jsdocQuery?.sections,
984
+ tags: jsdocQuery?.tags,
985
+ })
986
+ log(` ↳ ${entry}`)
987
+ }
988
+ }
989
+ }
990
+
991
+ // Show type aliases
992
+ if (Object.keys(typeInfo.types).length > 0) {
993
+ log(`\n 📝 Type Aliases:`)
994
+ for (const [name, definition] of Object.entries(typeInfo.types)) {
995
+ const shortDef = definition.length > 80 ? definition.substring(0, 80) + "..." : definition
996
+ log(` type ${name} = ${shortDef}`)
997
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
998
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
999
+ mode: jsdocMode,
1000
+ truncate: jsdocQuery?.truncate,
1001
+ maxLen: jsdocQuery?.maxLen,
1002
+ sections: jsdocQuery?.sections,
1003
+ tags: jsdocQuery?.tags,
1004
+ })
1005
+ log(` ↳ ${entry}`)
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ // Show class inheritance
1011
+ if (Object.keys(typeInfo.classes).length > 0) {
1012
+ log(`\n 🏛️ Class Definitions:`)
1013
+ for (const [name, extendsClass] of Object.entries(typeInfo.classes)) {
1014
+ const inheritance = extendsClass ? ` extends ${extendsClass}` : ""
1015
+ log(` class ${name}${inheritance}`)
1016
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
1017
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
1018
+ mode: jsdocMode,
1019
+ truncate: jsdocQuery?.truncate,
1020
+ maxLen: jsdocQuery?.maxLen,
1021
+ sections: jsdocQuery?.sections,
1022
+ tags: jsdocQuery?.tags,
1023
+ })
1024
+ log(` ↳ ${entry}`)
1025
+ }
1026
+ }
1027
+ }
1028
+
1029
+ if (Object.keys(typeInfo.enums).length > 0) {
1030
+ log(`\n 🧾 Enums:`)
1031
+ for (const [name, members] of Object.entries(typeInfo.enums)) {
1032
+ const preview = members.length > 0 ? ` = [${members.join(", ")}]` : ""
1033
+ log(` enum ${name}${preview}`)
1034
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
1035
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
1036
+ mode: jsdocMode,
1037
+ truncate: jsdocQuery?.truncate,
1038
+ maxLen: jsdocQuery?.maxLen,
1039
+ sections: jsdocQuery?.sections,
1040
+ tags: jsdocQuery?.tags,
1041
+ })
1042
+ log(` ↳ ${entry}`)
1043
+ }
1044
+ }
1045
+ }
1046
+
1047
+ if (Object.keys(typeInfo.namespaces).length > 0) {
1048
+ log(`\n 📦 Namespaces:`)
1049
+ for (const name of Object.keys(typeInfo.namespaces)) {
1050
+ log(` namespace ${name}`)
1051
+ if (jsdocOutput === "inline" && typeInfo.jsdoc?.[name]) {
1052
+ const entry = formatJsdocEntry(name, typeInfo.jsdoc[name], {
1053
+ mode: jsdocMode,
1054
+ truncate: jsdocQuery?.truncate,
1055
+ maxLen: jsdocQuery?.maxLen,
1056
+ sections: jsdocQuery?.sections,
1057
+ tags: jsdocQuery?.tags,
1058
+ })
1059
+ log(` ↳ ${entry}`)
1060
+ }
1061
+ }
1062
+ }
1063
+
1064
+ if (typeInfo.defaults.length > 0) {
1065
+ log(`\n 📦 Default Exports:`)
1066
+ typeInfo.defaults.slice(0, 5).forEach((value) => log(` default = ${value}`))
1067
+ }
1068
+ }
1069
+
1070
+ if (wantJsdoc && jsdocMode !== "off" && typeInfo.jsdoc && Object.keys(typeInfo.jsdoc).length > 0) {
1071
+ const symbolMatcher = buildSymbolMatcher(jsdocQuery?.symbols, filter)
1072
+ const entries = Object.entries(typeInfo.jsdoc)
1073
+ .filter(([name]) => (symbolMatcher ? symbolMatcher(name) : true))
1074
+ .slice(0, 50)
1075
+
1076
+ if (entries.length > 0) {
1077
+ log(`\n 📚 JSDoc:`)
1078
+ for (const [name, doc] of entries) {
1079
+ const entry = formatJsdocEntry(name, doc, {
1080
+ mode: jsdocMode,
1081
+ truncate: jsdocQuery?.truncate,
1082
+ maxLen: jsdocQuery?.maxLen,
1083
+ sections: jsdocQuery?.sections,
1084
+ tags: jsdocQuery?.tags,
1085
+ })
1086
+ log(` ${entry}`)
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ if (jsdocOutput !== "only") {
1092
+ const typeExportNames = new Set([
1093
+ ...Object.keys(typeInfo.functions),
1094
+ ...Object.keys(typeInfo.interfaces),
1095
+ ...Object.keys(typeInfo.types),
1096
+ ...Object.keys(typeInfo.classes),
1097
+ ...Object.keys(typeInfo.enums),
1098
+ ...Object.keys(typeInfo.namespaces),
1099
+ ])
1100
+ if (runtimeAvailable) {
1101
+ const runtimeNames = new Set(allExports)
1102
+ const runtimeOnly = [...runtimeNames].filter((name) => !typeExportNames.has(name))
1103
+ const typesOnly = [...typeExportNames].filter((name) => !runtimeNames.has(name))
1104
+
1105
+ if (runtimeOnly.length > 0 || typesOnly.length > 0) {
1106
+ log(`\n ⚖️ Runtime/Types Mismatch:`)
1107
+ if (runtimeOnly.length > 0) {
1108
+ log(` Runtime only: ${runtimeOnly.slice(0, 10).join(", ")}`)
1109
+ }
1110
+ if (typesOnly.length > 0) {
1111
+ log(` Types only: ${typesOnly.slice(0, 10).join(", ")}`)
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ if (
1117
+ Object.keys(typeInfo.functions).length === 0 &&
1118
+ Object.keys(typeInfo.interfaces).length === 0 &&
1119
+ Object.keys(typeInfo.types).length === 0 &&
1120
+ Object.keys(typeInfo.classes).length === 0 &&
1121
+ Object.keys(typeInfo.enums).length === 0 &&
1122
+ Object.keys(typeInfo.namespaces).length === 0 &&
1123
+ typeInfo.defaults.length === 0
1124
+ ) {
1125
+ log(` ⚠️ No type definitions found for filtered exports`)
1126
+ }
1127
+ }
1128
+ } else {
1129
+ if (jsdocOutput !== "only") {
1130
+ log(` ⚠️ Could not parse type definitions`)
1131
+ }
1132
+ }
1133
+ } else {
1134
+ if (jsdocOutput !== "only") {
1135
+ log(`\n⚠️ Type definitions not available for this package`)
1136
+ }
1137
+ }
1138
+ }
1139
+ } catch (e) {
1140
+ logErr(`\n❌ Erro: ${e.message}`)
1141
+ logErr(`Certifique-se que '${target}' está instalado e é um caminho válido.`)
1142
+ }
1143
+
1144
+ return collect ? output.join("\n") : ""
1145
+ }