import glob from 'glob' ; import { partition , flatMap , isString , toPairs , fromPairs , sortBy } from 'lodash' ; import { exploreBundle , UNMAPPED_KEY , SPECIAL_FILENAMES } from './explore' ; import { AppError , getErrorMessage } from './app-error' ; import { formatOutput , saveOutputToFile } from './output' ; import { addCoverageRanges } from './coverage' ; import type { Bundle , BundlesAndFileTokens , ExploreBundleResult , ExploreErrorResult , ExploreOptions , ExploreResult , } from './types' ; function adjustOptions ( options ) { if ( options . gzip ) { options . onlyMapped = true ; } return options ; } /**
 * Analyze bundle(s)
 */ export async function explore ( bundlesAndFileTokens , options = { } ) { bundlesAndFileTokens = Array . isArray ( bundlesAndFileTokens ) ? bundlesAndFileTokens : [ bundlesAndFileTokens ] ; if ( bundlesAndFileTokens . length === 0 ) { throw new AppError ( { code : 'NoBundles' } ) ; } adjustOptions ( options ) ; const [ fileTokens , bundles ] = partition ( bundlesAndFileTokens , isString ) ; bundles . push ( ... getBundles ( fileTokens ) ) ; addCoverageRanges ( bundles , options . coverage ) ; const results = await Promise . all ( bundles . map ( ( bundle ) => exploreBundle ( bundle , options ) . catch < ExploreErrorResult > ( ( error ) => onExploreError ( bundle , error ) ) ) ) ; const exploreResult = getExploreResult ( results , options ) ; if ( exploreResult . bundles . length === 0 ) { return Promise . reject ( exploreResult ) ; } saveOutputToFile ( exploreResult , options ) ; return exploreResult ; } /**
 * Expand list of file tokens into a list of bundles
 */ export function getBundles ( fileTokens ) { const filenames = flatMap ( fileTokens , ( filePath ) => glob . hasMagic ( filePath ) ? expandGlob ( filePath ) : filePath ) ; const [ mapFilenames , codeFilenames ] = partition ( filenames , ( filename ) => filename . endsWith ( '.map' ) ) ; return codeFilenames . map < Bundle > ( ( code ) => ( { code , map : mapFilenames . find ( ( filename ) => filename === `${ code }.map` ) , } ) ) ; } function expandGlob ( pattern ) { if ( pattern . endsWith ( '.js' ) ) { pattern = `${ pattern }?(.map)` ; } return glob . sync ( pattern ) ; } export function getBundleName ( bundle ) { return Buffer . isBuffer ( bundle . code ) ? 'Buffer' : bundle . code ; } /**
 * Handle error during bundle processing
 */ function onExploreError ( bundle , error ) { return { bundleName : getBundleName ( bundle ) , code : error . code || 'Unknown' , message : error . message , error , } ; } function sortFilenames ( bundles ) { return bundles . map ( ( bundle ) => ( { ... bundle , files : fromPairs ( sortBy ( toPairs ( bundle . files ) , 0 ) ) , } ) ) ; } function getExploreResult ( results , options ) { const [ bundles , errors ] = partition ( results , ( result ) : result is ExploreBundleResult => 'files' in result ) ; let sortedBundles = sortBy ( bundles , ( bundle ) => bundle . bundleName ) ; if ( options . sort ) { sortedBundles = sortFilenames ( sortedBundles ) ; } errors . push ( ... getPostExploreErrors ( bundles ) ) ; return { bundles : sortedBundles , errors , ... ( bundles . length > 0 && { output : formatOutput ( sortedBundles , options ) } ) , } ; } function getPostExploreErrors ( exploreBundleResults ) { const errors = [ ] ; const isSingleBundle = exploreBundleResults . length === 1 ; for ( const result of exploreBundleResults ) { const { bundleName , files , totalBytes } = result ; if ( isSingleBundle ) { const filenames = Object . keys ( files ) . filter ( ( filename ) => ! SPECIAL_FILENAMES . includes ( filename ) ) ; if ( filenames . length === 1 ) { errors . push ( { bundleName , isWarning : true , code : 'OneSourceSourceMap' , message : getErrorMessage ( { code : 'OneSourceSourceMap' , filename : filenames [ 0 ] } ) , } ) ; } } if ( files [ UNMAPPED_KEY ] !== undefined ) { const { size : unmappedBytes } = files [ UNMAPPED_KEY ] ; if ( unmappedBytes ) { errors . push ( { bundleName , isWarning : true , code : 'UnmappedBytes' , message : getErrorMessage ( { code : 'UnmappedBytes' , unmappedBytes , totalBytes } ) , } ) ; } } } return errors ; }