import { join } from 'path' ; import { readdirSync , lstatSync , existsSync } from 'fs' ; import { spawnSync } from 'child_process' ; import { BuildPackage } from './build-package' ; import { platform } from 'os' ; param pkg returns export function getSecondaryEntryPointsForPackage ( pkg ) { const packageName = pkg . name ; const packageDir = pkg . sourceDir ; const entryPoints = getSubdirectoryNames ( packageDir ) . filter ( d => existsSync ( join ( packageDir , d , 'tsconfig-build.json' ) ) ) ; const buildNodes = entryPoints . map ( p => ( { name : p , deps : [ ] , depth : 0 } ) ) ; const nodeLookup = buildNodes . reduce ( ( lookup , node ) => { return lookup . set ( node . name , node ) ; } , new Map < string , BuildNode > ( ) ) ; const importRegex = new RegExp ( `${ packageName }/(.+)';` ) ; buildNodes . forEach ( node => { const importStatementFindCommand = buildPackageImportStatementFindCommand ( join ( packageDir , node . name ) , packageName ) ; node . deps = spawnSync ( importStatementFindCommand . binary , importStatementFindCommand . args ) . stdout . toString ( ) . split ( '\n' ) . filter ( n => n ) . map ( importStatement => importStatement . match ( importRegex ) ! [ 1 ] ) . filter ( n => nodeLookup . has ( n ) && n !== node . name ) . map ( depName => nodeLookup . get ( depName ) ! ) || [ ] ; } ) ; const buildOrder = buildNodes . reduce ( ( order , node ) => { return [ ... order , ... getBuildOrder ( node ) ] ; } , [ ] ) ; return partitionNodesByDepth ( buildOrder ) . map ( level => level . map ( node => node . name ) ) ; } /** Gets the build order for node with DFS. As a side-effect, sets the depth on visited nodes. */ function getBuildOrder ( node ) { if ( node . visited ) { return [ ] ; } let buildOrder = [ ] ; for ( const dep of node . deps ) { buildOrder = [ ... buildOrder , ... getBuildOrder ( dep ) ] ; node . depth = node . deps . reduce ( ( maxDepth , d ) => Math . max ( d . depth + 1 , maxDepth ) , - 1 ) ; } node . visited = true ; return [ ... buildOrder , node ] ; } /** Gets the names of all subdirectories for a given path. */ export function getSubdirectoryNames ( dir ) { return readdirSync ( dir ) . filter ( f => lstatSync ( join ( dir , f ) ) . isDirectory ( ) ) ; } /** A node in the build graph of a package's entry-points. */ interface BuildNode { name : string ; deps : BuildNode [ ] ; visited ? : boolean ; depth : number ; } /**
 * `Partitions nodes into groups by depth. For example,
 * [{name: a11y, depth: 1}, {name: bidi, depth: 0}, {name: platform, depth: 0}]
 * =>
 * [ [{name: bidi, depth: 0}, {name: platform, depth: 0}], [{name: a11y, depth: 1}] ]`
 */ function partitionNodesByDepth ( nodes ) { const result = [ [ ] ] ; let lastDepth = 0 ; nodes . sort ( ( a , b ) => a . depth - b . depth ) . forEach ( node => { if ( node . depth === lastDepth ) { result [ result . length - 1 ] . push ( node ) ; } else { result . push ( [ node ] ) ; lastDepth = node . depth ; } } ) ; return result ; } /** Builds the command that will be executed to find all import statements for a package. */ function buildPackageImportStatementFindCommand ( searchDirectory , packageName ) { if ( platform ( ) === 'win32' ) { return { binary : 'findstr' , args : [ '/r' , `from.'@angular/${ packageName }/.*'` , `${ searchDirectory }\\*.ts` ] } ; } else { return { binary : 'grep' , args : [ '-Eroh' , '--include' , '*.ts' , `from '@angular/${ packageName }/.+';` , searchDirectory ] } ; } }