1
1
import * as path from 'path'
2
2
import { Command , Flags , Plugin } from '@oclif/core'
3
3
import * as chalk from 'chalk'
4
- import { exec } from 'child_process'
5
4
import * as fs from 'fs-extra'
5
+ import { cli } from 'cli-ux'
6
6
7
7
import Plugins from '../../plugins'
8
8
import { sortBy } from '../../util'
9
9
10
- const TAB = ' '
11
-
12
- type Dependencies = {
13
- [ key : string ] : unknown ;
14
- from ?: string ;
15
- version ?: string ;
16
- name ?: string ;
17
- dependencies : {
18
- [ key : string ] : Dependencies ;
19
- } ;
10
+ function trimUntil ( fsPath : string , part : string ) : string {
11
+ const parts = fsPath . split ( path . sep )
12
+ const indicies = parts . reduce ( ( a , e , i ) => ( e === part ) ? a . concat ( [ i ] ) : a , [ ] as number [ ] )
13
+ const partIndex = Math . max ( ...indicies )
14
+ if ( partIndex === - 1 ) return fsPath
15
+ return parts . slice ( 0 , partIndex + 1 ) . join ( path . sep )
20
16
}
21
17
22
18
export default class PluginsInspect extends Command {
@@ -41,13 +37,10 @@ export default class PluginsInspect extends Command {
41
37
42
38
plugins = new Plugins ( this . config ) ;
43
39
44
- allDeps : Dependencies = { dependencies : { } } ;
45
-
46
40
// In this case we want these operations to happen
47
41
// sequentially so the `no-await-in-loop` rule is ugnored
48
42
/* eslint-disable no-await-in-loop */
49
43
async run ( ) {
50
- this . allDeps = await this . npmList ( this . config . root , 3 )
51
44
const { flags, argv} = await this . parse ( PluginsInspect )
52
45
if ( flags . verbose ) this . plugins . verbose = true
53
46
const aliases = this . config . pjson . oclif . aliases || { }
@@ -61,7 +54,7 @@ export default class PluginsInspect extends Command {
61
54
const pluginName = await this . parsePluginName ( name )
62
55
63
56
try {
64
- await this . inspect ( pluginName )
57
+ await this . inspect ( pluginName , flags . verbose )
65
58
} catch ( error ) {
66
59
this . log ( chalk . bold . red ( 'failed' ) )
67
60
throw error
@@ -88,77 +81,58 @@ export default class PluginsInspect extends Command {
88
81
throw new Error ( `${ pluginName } not installed` )
89
82
}
90
83
91
- async inspect ( pluginName : string ) {
84
+ async inspect ( pluginName : string , verbose = false ) {
92
85
const plugin = this . findPlugin ( pluginName )
93
- this . log ( chalk . bold . cyan ( plugin . name ) )
94
-
95
- this . log ( `${ TAB } version: ${ plugin . version } ` )
96
- if ( plugin . tag ) this . log ( `${ TAB } tag: ${ plugin . tag } ` )
97
- if ( plugin . pjson . homepage ) this . log ( `${ TAB } homepage: ${ plugin . pjson . homepage } ` )
98
- this . log ( `${ TAB } location: ${ plugin . root } ` )
99
-
100
- this . log ( `${ TAB } commands:` )
86
+ const tree = cli . tree ( )
87
+ const pluginHeader = chalk . bold . cyan ( plugin . name )
88
+ tree . insert ( pluginHeader )
89
+ tree . nodes [ pluginHeader ] . insert ( `version ${ plugin . version } ` )
90
+ if ( plugin . tag ) tree . nodes [ pluginHeader ] . insert ( `tag ${ plugin . tag } ` )
91
+ if ( plugin . pjson . homepage ) tree . nodes [ pluginHeader ] . insert ( `homepage ${ plugin . pjson . homepage } ` )
92
+ tree . nodes [ pluginHeader ] . insert ( `location ${ plugin . root } ` )
93
+
94
+ tree . nodes [ pluginHeader ] . insert ( 'commands' )
101
95
const commands = sortBy ( plugin . commandIDs , c => c )
102
- commands . forEach ( cmd => this . log ( ` ${ TAB . repeat ( 2 ) } ${ cmd } ` ) )
96
+ commands . forEach ( cmd => tree . nodes [ pluginHeader ] . nodes . commands . insert ( cmd ) )
103
97
104
- const dependencies = plugin . root . includes ( this . config . root ) ?
105
- this . findDepInTree ( plugin ) . dependencies :
106
- ( await this . npmList ( plugin . root ) ) . dependencies
98
+ const dependencies = Object . assign ( { } , plugin . pjson . dependencies )
107
99
108
- this . log ( ` ${ TAB } dependencies:` )
100
+ tree . nodes [ pluginHeader ] . insert ( ' dependencies' )
109
101
const deps = sortBy ( Object . keys ( dependencies ) , d => d )
110
102
for ( const dep of deps ) {
111
103
// eslint-disable-next-line no-await-in-loop
112
- const version = dependencies [ dep ] . version || await this . findDepInSharedModules ( plugin , dep )
113
- const from = dependencies [ dep ] . from ?
114
- dependencies [ dep ] . from ! . split ( '@' ) . reverse ( ) [ 0 ] :
115
- null
104
+ const { version, pkgPath} = await this . findDep ( plugin , dep )
105
+ if ( ! version ) continue
116
106
117
- if ( from ) this . log ( `${ TAB . repeat ( 2 ) } ${ dep } : ${ from } => ${ version } ` )
118
- else this . log ( `${ TAB . repeat ( 2 ) } ${ dep } : ${ version } ` )
119
- }
120
- }
107
+ const from = dependencies [ dep ] ?? null
108
+ const versionMsg = chalk . dim ( from ? `${ from } => ${ version } ` : version )
109
+ const msg = verbose ? `${ dep } ${ versionMsg } ${ pkgPath } ` : `${ dep } ${ versionMsg } `
121
110
122
- async findDepInSharedModules ( plugin : Plugin , dependency : string ) : Promise < string > {
123
- const sharedModulePath = path . join ( plugin . root . replace ( plugin . name , '' ) , ...dependency . split ( '/' ) , 'package.json' )
124
- const pkgJson = JSON . parse ( await fs . readFile ( sharedModulePath , 'utf-8' ) )
125
- return pkgJson . version as string
111
+ tree . nodes [ pluginHeader ] . nodes . dependencies . insert ( msg )
112
+ }
113
+ tree . display ( )
126
114
}
127
115
128
- findDepInTree ( plugin : Plugin ) : Dependencies {
129
- if ( plugin . name === this . allDeps . name ) return this . allDeps
130
- const plugins = [ plugin . name ]
131
- let p = plugin
132
- while ( p . parent ) {
133
- plugins . push ( p . parent . name )
134
- p = p . parent
116
+ async findDep ( plugin : Plugin , dependency : string ) : Promise < { version : string | null ; pkgPath : string | null } > {
117
+ const dependencyPath = path . join ( ... dependency . split ( '/' ) )
118
+ let start = path . join ( plugin . root , 'node_modules' )
119
+ const paths = [ start ]
120
+ while ( ( start . match ( / n o d e _ m o d u l e s / g ) || [ ] ) . length > 1 ) {
121
+ start = trimUntil ( path . dirname ( start ) , 'node_modules' )
122
+ paths . push ( start )
135
123
}
136
124
137
- let dependencies = this . allDeps
138
- for ( const plg of plugins . reverse ( ) ) {
139
- dependencies = dependencies . dependencies [ plg ]
125
+ for ( const p of paths ) {
126
+ const fullPath = path . join ( p , dependencyPath )
127
+ const pkgJsonPath = path . join ( fullPath , 'package.json' )
128
+ try {
129
+ // eslint-disable-next-line no-await-in-loop
130
+ const pkgJson = JSON . parse ( await fs . readFile ( pkgJsonPath , 'utf-8' ) )
131
+ return { version : pkgJson . version as string , pkgPath : fullPath }
132
+ } catch {
133
+ // try the next path
134
+ }
140
135
}
141
- return dependencies
142
- }
143
-
144
- async npmList ( cwd : string , depth = 0 ) : Promise < Dependencies > {
145
- return new Promise ( ( resolve , reject ) => {
146
- exec ( `npm list --json --depth ${ depth } ` , {
147
- cwd,
148
- encoding : 'utf-8' ,
149
- maxBuffer : 2048 * 2048 ,
150
- } , ( error , stdout ) => {
151
- if ( error ) {
152
- try {
153
- const parsed = JSON . parse ( stdout )
154
- if ( parsed ) resolve ( parsed )
155
- } catch {
156
- reject ( new Error ( `Could not get dependencies for ${ cwd } ` ) )
157
- }
158
- } else {
159
- resolve ( JSON . parse ( stdout ) )
160
- }
161
- } )
162
- } )
136
+ return { version : null , pkgPath : null }
163
137
}
164
138
}
0 commit comments