3
3
4
4
import * as path from 'path' ;
5
5
import builtinPackageNames from 'builtin-modules' ;
6
- import { Colorize } from '@rushstack/terminal' ;
7
- import type { CommandLineFlagParameter } from '@rushstack/ts-command-line' ;
8
- import { FileSystem } from '@rushstack/node-core-library' ;
6
+ import { Colorize , type ITerminal } from '@rushstack/terminal' ;
7
+ import type { CommandLineFlagParameter , CommandLineStringListParameter } from '@rushstack/ts-command-line' ;
8
+ import { FileSystem , FileConstants , JsonFile } from '@rushstack/node-core-library' ;
9
+ import type FastGlob from 'fast-glob' ;
9
10
10
11
import type { RushCommandLineParser } from '../RushCommandLineParser' ;
11
12
import { BaseConfiglessRushAction } from './BaseRushAction' ;
13
+ import type { RushConfigurationProject } from '../../api/RushConfigurationProject' ;
12
14
13
- export interface IJsonOutput {
15
+ export interface IScanResult {
14
16
/**
15
17
* Dependencies scan from source code
16
18
*/
@@ -26,8 +28,11 @@ export interface IJsonOutput {
26
28
}
27
29
28
30
export class ScanAction extends BaseConfiglessRushAction {
31
+ private readonly _terminal : ITerminal ;
29
32
private readonly _jsonFlag : CommandLineFlagParameter ;
30
33
private readonly _allFlag : CommandLineFlagParameter ;
34
+ private readonly _folders : CommandLineStringListParameter ;
35
+ private readonly _projects : CommandLineStringListParameter ;
31
36
32
37
public constructor ( parser : RushCommandLineParser ) {
33
38
super ( {
@@ -40,7 +45,7 @@ export class ScanAction extends BaseConfiglessRushAction {
40
45
` declaring them as dependencies in the package.json file. Such "phantom dependencies"` +
41
46
` can cause problems. Rush and PNPM use symlinks specifically to protect against phantom dependencies.` +
42
47
` These protections may cause runtime errors for existing projects when they are first migrated into` +
43
- ` a Rush monorepo. The "rush scan" command is a handy tool for fixing these errors. It scans the "./src"` +
48
+ ` a Rush monorepo. The "rush scan" command is a handy tool for fixing these errors. It default scans the "./src"` +
44
49
` and "./lib" folders for import syntaxes such as "import __ from '__'", "require('__')",` +
45
50
` and "System.import('__'). It prints a report of the referenced packages. This heuristic is` +
46
51
` not perfect, but it can save a lot of time when migrating projects.` ,
@@ -56,14 +61,31 @@ export class ScanAction extends BaseConfiglessRushAction {
56
61
parameterLongName : '--all' ,
57
62
description : 'If this flag is specified, output will list all detected dependencies.'
58
63
} ) ;
64
+ this . _folders = this . defineStringListParameter ( {
65
+ parameterLongName : '--folder' ,
66
+ parameterShortName : '-f' ,
67
+ argumentName : 'FOLDER' ,
68
+ description :
69
+ 'The folders that need to be scanned, default is src and lib.' +
70
+ 'Normally we can input all the folders under the project directory, excluding the ignored folders.'
71
+ } ) ;
72
+ this . _projects = this . defineStringListParameter ( {
73
+ parameterLongName : '--only' ,
74
+ parameterShortName : '-o' ,
75
+ argumentName : 'PROJECT' ,
76
+ description : 'Projects that need to be checked for phantom dependencies.'
77
+ } ) ;
78
+ this . _terminal = parser . terminal ;
59
79
}
60
80
61
- protected async runAsync ( ) : Promise < void > {
62
- const packageJsonFilename : string = path . resolve ( './package.json' ) ;
63
-
64
- if ( ! FileSystem . exists ( packageJsonFilename ) ) {
65
- throw new Error ( 'You must run "rush scan" in a project folder containing a package.json file.' ) ;
66
- }
81
+ private async _scanAsync ( params : {
82
+ packageJsonFilePath : string ;
83
+ folders : readonly string [ ] ;
84
+ glob : typeof FastGlob ;
85
+ terminal : ITerminal ;
86
+ } ) : Promise < IScanResult > {
87
+ const { packageJsonFilePath, folders, glob, terminal } = params ;
88
+ const packageJsonFilename : string = path . resolve ( packageJsonFilePath ) ;
67
89
68
90
const requireRegExps : RegExp [ ] = [
69
91
// Example: require('something')
@@ -114,8 +136,13 @@ export class ScanAction extends BaseConfiglessRushAction {
114
136
115
137
const requireMatches : Set < string > = new Set < string > ( ) ;
116
138
117
- const { default : glob } = await import ( 'fast-glob' ) ;
118
- const scanResults : string [ ] = await glob ( [ './*.{ts,js,tsx,jsx}' , './{src,lib}/**/*.{ts,js,tsx,jsx}' ] ) ;
139
+ const scanResults : string [ ] = await glob (
140
+ [
141
+ './*.{ts,js,tsx,jsx}' ,
142
+ `./${ folders . length > 1 ? '{' + folders . join ( ',' ) + '}' : folders [ 0 ] } /**/*.{ts,js,tsx,jsx}`
143
+ ] ,
144
+ { cwd : path . dirname ( packageJsonFilePath ) , absolute : true }
145
+ ) ;
119
146
for ( const filename of scanResults ) {
120
147
try {
121
148
const contents : string = FileSystem . readFile ( filename ) ;
@@ -131,7 +158,8 @@ export class ScanAction extends BaseConfiglessRushAction {
131
158
}
132
159
} catch ( error ) {
133
160
// eslint-disable-next-line no-console
134
- console . log ( Colorize . bold ( 'Skipping file due to error: ' + filename ) ) ;
161
+ console . log ( error ) ;
162
+ terminal . writeErrorLine ( Colorize . bold ( 'Skipping file due to error: ' + filename ) ) ;
135
163
}
136
164
}
137
165
@@ -175,8 +203,7 @@ export class ScanAction extends BaseConfiglessRushAction {
175
203
}
176
204
}
177
205
} catch ( e ) {
178
- // eslint-disable-next-line no-console
179
- console . error ( `JSON.parse ${ packageJsonFilename } error` ) ;
206
+ terminal . writeErrorLine ( `JSON.parse ${ packageJsonFilename } error` ) ;
180
207
}
181
208
182
209
for ( const detectedPkgName of detectedPackageNames ) {
@@ -200,65 +227,108 @@ export class ScanAction extends BaseConfiglessRushAction {
200
227
}
201
228
}
202
229
203
- const output : IJsonOutput = {
230
+ const output : IScanResult = {
204
231
detectedDependencies : detectedPackageNames ,
205
232
missingDependencies : missingDependencies ,
206
233
unusedDependencies : unusedDependencies
207
234
} ;
208
235
236
+ return output ;
237
+ }
238
+
239
+ private _getPackageJsonPathsFromProjects ( projectNames : readonly string [ ] ) : string [ ] {
240
+ const result : string [ ] = [ ] ;
241
+ if ( ! this . rushConfiguration ) {
242
+ throw new Error ( `` ) ;
243
+ }
244
+ for ( const projectName of projectNames ) {
245
+ const project : RushConfigurationProject | undefined =
246
+ this . rushConfiguration . getProjectByName ( projectName ) ;
247
+ if ( ! project ) {
248
+ throw new Error ( `` ) ;
249
+ }
250
+ const packageJsonFilePath : string = path . join ( project . projectFolder , FileConstants . PackageJson ) ;
251
+ result . push ( packageJsonFilePath ) ;
252
+ }
253
+ return result ;
254
+ }
255
+
256
+ protected async runAsync ( ) : Promise < void > {
257
+ const packageJsonFilePaths : string [ ] = this . _projects . values . length
258
+ ? this . _getPackageJsonPathsFromProjects ( this . _projects . values )
259
+ : [ path . resolve ( './package.json' ) ] ;
260
+ const { default : glob } = await import ( 'fast-glob' ) ;
261
+ const folders : readonly string [ ] = this . _folders . values . length ? this . _folders . values : [ 'src' , 'lib' ] ;
262
+
263
+ const output : Record < string , IScanResult > = { } ;
264
+
265
+ for ( const packageJsonFilePath of packageJsonFilePaths ) {
266
+ if ( ! FileSystem . exists ( packageJsonFilePath ) ) {
267
+ throw new Error ( `${ packageJsonFilePath } is not exist` ) ;
268
+ }
269
+ const packageName : string = JsonFile . load ( packageJsonFilePath ) . name ;
270
+ const scanResult : IScanResult = await this . _scanAsync ( {
271
+ packageJsonFilePath,
272
+ folders,
273
+ glob,
274
+ terminal : this . _terminal
275
+ } ) ;
276
+ output [ packageName ] = scanResult ;
277
+ }
209
278
if ( this . _jsonFlag . value ) {
210
- // eslint-disable-next-line no-console
211
- console . log ( JSON . stringify ( output , undefined , 2 ) ) ;
279
+ this . _terminal . writeLine ( JSON . stringify ( output , undefined , 2 ) ) ;
212
280
} else if ( this . _allFlag . value ) {
213
- if ( detectedPackageNames . length !== 0 ) {
214
- // eslint-disable-next-line no-console
215
- console . log ( 'Dependencies that seem to be imported by this project:' ) ;
216
- for ( const packageName of detectedPackageNames ) {
217
- // eslint-disable-next-line no-console
218
- console . log ( ' ' + packageName ) ;
281
+ for ( const [ packageName , scanResult ] of Object . entries ( output ) ) {
282
+ this . _terminal . writeLine ( `-------------------- ${ packageName } result start --------------------` ) ;
283
+ const { detectedDependencies } = scanResult ;
284
+ if ( detectedDependencies . length !== 0 ) {
285
+ this . _terminal . writeLine ( `Dependencies that seem to be imported by this project ${ packageName } :` ) ;
286
+ for ( const detectedDependency of detectedDependencies ) {
287
+ this . _terminal . writeLine ( ' ' + detectedDependency ) ;
288
+ }
289
+ } else {
290
+ this . _terminal . writeLine ( `This project ${ packageName } does not seem to import any NPM packages.` ) ;
219
291
}
220
- } else {
221
- // eslint-disable-next-line no-console
222
- console . log ( 'This project does not seem to import any NPM packages.' ) ;
292
+ this . _terminal . writeLine ( `-------------------- ${ packageName } result end --------------------` ) ;
223
293
}
224
294
} else {
225
- let wroteAnything : boolean = false ;
226
-
227
- if ( missingDependencies . length > 0 ) {
228
- // eslint-disable-next-line no-console
229
- console . log (
230
- Colorize . yellow ( 'Possible phantom dependencies' ) +
231
- " - these seem to be imported but aren't listed in package.json:"
232
- ) ;
233
- for ( const packageName of missingDependencies ) {
234
- // eslint-disable-next-line no-console
235
- console . log ( ' ' + packageName ) ;
295
+ for ( const [ packageName , scanResult ] of Object . entries ( output ) ) {
296
+ this . _terminal . writeLine ( `-------------------- ${ packageName } result start --------------------` ) ;
297
+ const { missingDependencies, unusedDependencies } = scanResult ;
298
+ let wroteAnything : boolean = false ;
299
+
300
+ if ( missingDependencies . length > 0 ) {
301
+ this . _terminal . writeWarningLine (
302
+ Colorize . yellow ( 'Possible phantom dependencies' ) +
303
+ " - these seem to be imported but aren't listed in package.json:"
304
+ ) ;
305
+ for ( const missingDependency of missingDependencies ) {
306
+ this . _terminal . writeLine ( ' ' + missingDependency ) ;
307
+ }
308
+ wroteAnything = true ;
236
309
}
237
- wroteAnything = true ;
238
- }
239
310
240
- if ( unusedDependencies . length > 0 ) {
241
- if ( wroteAnything ) {
242
- // eslint-disable-next-line no-console
243
- console . log ( '' ) ;
311
+ if ( unusedDependencies . length > 0 ) {
312
+ if ( wroteAnything ) {
313
+ this . _terminal . writeLine ( '' ) ;
314
+ }
315
+ this . _terminal . writeWarningLine (
316
+ Colorize . yellow ( 'Possible unused dependencies' ) +
317
+ " - these are listed in package.json but don't seem to be imported:"
318
+ ) ;
319
+ for ( const unusedDependency of unusedDependencies ) {
320
+ this . _terminal . writeLine ( ' ' + unusedDependency ) ;
321
+ }
322
+ wroteAnything = true ;
244
323
}
245
- // eslint-disable-next-line no-console
246
- console . log (
247
- Colorize . yellow ( 'Possible unused dependencies' ) +
248
- " - these are listed in package.json but don't seem to be imported:"
249
- ) ;
250
- for ( const packageName of unusedDependencies ) {
251
- // eslint-disable-next-line no-console
252
- console . log ( ' ' + packageName ) ;
324
+
325
+ if ( ! wroteAnything ) {
326
+ this . _terminal . writeLine (
327
+ Colorize . green ( 'Everything looks good.' ) + ' No missing or unused dependencies were found.'
328
+ ) ;
253
329
}
254
- wroteAnything = true ;
255
- }
256
330
257
- if ( ! wroteAnything ) {
258
- // eslint-disable-next-line no-console
259
- console . log (
260
- Colorize . green ( 'Everything looks good.' ) + ' No missing or unused dependencies were found.'
261
- ) ;
331
+ this . _terminal . writeLine ( `-------------------- ${ packageName } result end --------------------` ) ;
262
332
}
263
333
}
264
334
}
0 commit comments