@@ -35,8 +35,11 @@ function collectTsFiles(dir: string): string[] {
3535 return results ;
3636}
3737
38+ const isDir = fs . statSync ( resolved ) . isDirectory ( ) ;
39+ const boundary = isDir ? resolved : null ;
40+
3841let entryPoints : string [ ] ;
39- if ( fs . statSync ( resolved ) . isDirectory ( ) ) {
42+ if ( isDir ) {
4043 entryPoints = collectTsFiles ( resolved ) ;
4144} else {
4245 entryPoints = [ resolved ] ;
@@ -63,10 +66,17 @@ const JS_IMPORT_RE =
6366
6467function extractImports ( filePath : string ) : string [ ] {
6568 const content = fs . readFileSync ( filePath , "utf-8" ) ;
66- const { outputText } = ts . transpileModule ( content , {
67- compilerOptions : tsConfig ,
68- fileName : filePath ,
69- } ) ;
69+ let outputText : string ;
70+ try {
71+ ( { outputText } = ts . transpileModule ( content , {
72+ compilerOptions : tsConfig ,
73+ fileName : filePath ,
74+ } ) ) ;
75+ } catch {
76+ // Some files (e.g. declaration files) can't be transpiled — fall back to
77+ // regex on the original source, which still strips type-only imports.
78+ outputText = content ;
79+ }
7080 const specifiers : string [ ] = [ ] ;
7181 for ( const match of outputText . matchAll ( JS_IMPORT_RE ) ) {
7282 const spec = match [ 1 ] ;
@@ -204,6 +214,12 @@ function depthColor(depth: number): string {
204214
205215// --- Display ---
206216
217+ function leavesFolder ( filePath : string ) : boolean {
218+ if ( boundary === null ) return false ;
219+ if ( filePath . startsWith ( "@monkeytype/" ) ) return true ;
220+ return ! filePath . startsWith ( boundary + "/" ) ;
221+ }
222+
207223function displayPath ( filePath : string ) : string {
208224 if ( filePath . startsWith ( ROOT + "/" ) ) {
209225 return path . relative ( ROOT , filePath ) ;
@@ -224,9 +240,12 @@ function printTree(
224240 const connector = isRoot ? "" : isLast ? "└── " : "├── " ;
225241 const dc = depthColor ( depth ) ;
226242
243+ const leaves = ! isRoot && leavesFolder ( filePath ) ;
244+ const leavesTag = leaves ? ` ${ c . red } [↑]${ c . reset } ` : "" ;
245+
227246 if ( ! info ) {
228247 // leaf node (e.g. @monkeytype package)
229- console . log ( `${ c . dim } ${ prefix } ${ connector } ${ dp } ${ c . reset } ` ) ;
248+ console . log ( `${ c . dim } ${ prefix } ${ connector } ${ dp } ${ c . reset } ${ leavesTag } ` ) ;
230249 return ;
231250 }
232251
@@ -239,7 +258,7 @@ function printTree(
239258 const seen = ! isRoot && printed . has ( filePath ) ;
240259 const seenTag = seen ? ` ${ c . dim } [seen above]${ c . reset } ` : "" ;
241260 console . log (
242- `${ c . dim } ${ prefix } ${ connector } ${ c . reset } ${ nameStyle } ${ dp } ${ c . reset } ${ stats } ${ seenTag } ` ,
261+ `${ c . dim } ${ prefix } ${ connector } ${ c . reset } ${ nameStyle } ${ dp } ${ c . reset } ${ stats } ${ leavesTag } ${ seenTag } ` ,
243262 ) ;
244263
245264 if ( seen || depth >= maxDepthLimit ) return ;
@@ -350,3 +369,21 @@ console.log(
350369console . log (
351370 `Max depth: ${ c . bold } ${ maxDepthSeen } ${ c . reset } ${ c . dim } (${ displayPath ( maxDepthFile ) } )${ c . reset } ` ,
352371) ;
372+
373+ if ( boundary !== null ) {
374+ const externalDirect = new Set < string > ( ) ;
375+ const externalTransitive = new Set < string > ( ) ;
376+ for ( const entry of entryPoints ) {
377+ const info = cache . get ( entry ) ;
378+ if ( ! info ) continue ;
379+ for ( const dep of info . directImports ) {
380+ if ( leavesFolder ( dep ) ) externalDirect . add ( dep ) ;
381+ }
382+ for ( const dep of getAllReachable ( entry , new Set ( ) ) ) {
383+ if ( leavesFolder ( dep ) ) externalTransitive . add ( dep ) ;
384+ }
385+ }
386+ console . log (
387+ `Leaves folder ${ c . red } [↑]${ c . reset } : ${ c . bold } ${ externalDirect . size } ${ c . reset } direct, ${ c . bold } ${ externalTransitive . size } ${ c . reset } transitive` ,
388+ ) ;
389+ }
0 commit comments