import * as acorn from 'acorn' ; import flatten from 'array-flatten' ; import fs , { lstatSync } from 'fs' ; import path from 'path' ; import { Logger } from './logger' ; import IModuleCheckerConfig from './types/module-checker-config' ; import IModuleCheckerResult from './types/module-checker-result' ; import { IPackageJSON } from './types/package-json' ; export class ModulesChecker { public static readonly defaultConfig : IModuleCheckerConfig = { checkAllNodeModules : false , ignoreBabelAndWebpackPackages : true , logEs5Packages : false , silent : false } ; private logger : Logger ; constructor ( readonly dir , readonly config = ModulesChecker . defaultConfig ) { this . dir = path . resolve ( dir ) ; this . config = { ... ModulesChecker . defaultConfig , ... config } ; this . logger = new Logger ( config ) ; } public checkModules ( ) { return this . parseDeps ( ) ; } public parseDeps ( ) { const dependencies = this . getDeps ( ) || [ ] ; const es5Modules = [ ] ; const es6Modules = [ ] ; const ignored = [ ] ; dependencies . forEach ( dependency => { try { const dependencyIsEs5 = this . isScriptEs5 ( require . resolve ( dependency , { paths : [ this . dir ] } ) , dependency ) ; dependencyIsEs5 ? es5Modules . push ( dependency ) : es6Modules . push ( dependency ) ; } catch ( err ) { ignored . push ( dependency ) ; } } ) ; return { es5Modules , es6Modules , ignored } ; } public getDeps ( ) { let deps = this . getDepsFromRootPackageJson ( ) ; if ( this . config . checkAllNodeModules ) { deps . push ( ... this . getAllNodeModules ( ) ) ; } if ( this . config . ignoreBabelAndWebpackPackages ) { deps = this . dependenciesWithoutBabelAndWebpackPages ( deps ) ; } return [ ... new Set ( deps ) ] . sort ( ) ; } public isScriptEs5 ( scriptPath , dependencyName ) { const acornOpts = { ecmaVersion : 5 } ; const code = fs . readFileSync ( scriptPath , 'utf8' ) ; try { acorn . parse ( code , acornOpts ) ; } catch ( err ) { this . logger . log ( `❌ ${ dependencyName } is not ES5` ) ; return false ; } if ( this . config . logEs5Packages ) { this . logger . log ( `✅ ${ dependencyName } is ES5` ) ; } return true ; } private dependenciesWithoutBabelAndWebpackPages ( dependencies ) { const ignoreRegex = /(babel|webpack)|(loader$)/ ; return dependencies . filter ( dep => ! ignoreRegex . test ( dep ) ) ; } private getDepsFromRootPackageJson ( ) { const packageJsonPath = path . join ( this . dir , 'package.json' ) ; const packageJson = require ( packageJsonPath ) ; if ( ! packageJson ) { throw new Error ( `Failed to load package.json in ${ this . dir }` ) ; } return Object . keys ( packageJson . dependencies || { } ) ; } private getAllNodeModules ( ) { const nodeModulesPath = path . join ( this . dir , 'node_modules' ) ; if ( fs . existsSync ( nodeModulesPath ) ) { const isDirectory = ( source ) : boolean => lstatSync ( source ) . isDirectory ( ) ; const getDirectories = ( source ) : any [ ] => { return fs . readdirSync ( source ) . map ( name => path . join ( source , name ) ) . filter ( isDirectory ) ; } ; const getLeafFolderName = ( fullPath ) : string => { const needle = 'node_modules' + path . sep ; const indexOfLastSlash = fullPath . lastIndexOf ( needle ) ; return fullPath . substr ( indexOfLastSlash + needle . length ) ; } ; let nodeModules = getDirectories ( nodeModulesPath ) . filter ( entry => { const leafFolderName = getLeafFolderName ( entry ) ; return ! leafFolderName . startsWith ( '.' ) ; } ) . map ( entry => { if ( /@.*$/ . test ( entry ) ) { return getDirectories ( entry ) ; } else { return entry ; } } ) ; nodeModules = flatten ( nodeModules ) . map ( ( entry ) => getLeafFolderName ( entry ) ) ; return nodeModules ; } throw new Error ( `Failed to find node_modules at ${ this . dir }` ) ; } }