import * as fse from 'fs-extra' ; import * as path from 'path' ; import * as webpack from 'webpack' ; type DependencyEntry = { dependencies ? : { [ moduleName ] : DependencyEntry ; } ; requires ? : { [ moduleName ] : string ; } ; [ key ] : unknown ; } ; export type PackageLock = { dependencies ? : { [ key ] : DependencyEntry | undefined ; } ; [ key ] : unknown ; } ; type CopyEntry = { source : string ; destination : string ; } ; /**
 * Add instructions to the webpack configuration to exclude a given set of node_modules dependencies,
 * along with their dependencies.
 */ export function excludeNodeModulesAndDependencies ( projectRoot , webpackConfig , packageLockJson , moduleNames , log = ( ) : void => { } ) { const externalModulesClosure = getNodeModulesDependencyClosure ( packageLockJson , moduleNames ) ; const excludeEntries = getExternalsEntries ( externalModulesClosure ) ; const copyEntries = getNodeModuleCopyEntries ( projectRoot , externalModulesClosure ) ; webpackConfig . externals = webpackConfig . externals || { } ; log ( 'Excluded node modules (external node modules plus dependencies)' , externalModulesClosure ) ; Object . assign ( webpackConfig . externals , excludeEntries ) ; /**
     * Tell webpack to copy the given modules' sources into dist\node_modules so they can be found through normal require calls.
     *
     * NOTE:
     * copy-webpack-plugin doesn't preserve the executable bit, so we can't use it here. See https://github.com/microsoft/vscode-azuretools/pull/403 and https://github.com/webpack-contrib/copy-webpack-plugin/issues/35
     * filemanager-webpack-plugin is another option, but it's much less popular and has it's own problems like https://github.com/gregnb/filemanager-webpack-plugin/issues/94
     * We'll just create our own simple plugin leveraging "fs-extra" to copy the files
     */ webpackConfig . plugins = webpackConfig . plugins || [ ] ; webpackConfig . plugins . push ( { apply : ( compiler ) => { const pluginName = 'AzCodeCopyWorkaround' ; compiler . hooks . afterEmit . tapPromise ( pluginName , async ( ) => { await Promise . all ( copyEntries . map ( async ( ce ) => { const entryName = path . basename ( ce . source ) ; if ( await fse . pathExists ( ce . source ) ) { log ( `${ pluginName }: Copying "${ entryName }"...` ) ; await fse . copy ( ce . source , ce . destination ) ; } else { log ( `${ pluginName }: Ignoring "${ entryName }" because it doesn't exist` ) ; } } ) ) ; } ) ; } } ) ; } /**
 * Get the full set of node_modules modules plus their dependencies
 */ export function getNodeModulesDependencyClosure ( packageLock , moduleNames ) { const closure = new Set < string > ( ) ; const dependencies = packageLock . dependencies || < { [ key ] : DependencyEntry ; } > { } ; for ( const moduleName of moduleNames ) { if ( dependencies [ moduleName ] ) { closure . add ( moduleName ) ; const subdeps = getDependenciesFromEntry ( dependencies [ moduleName ] ! , packageLock ) ; for ( const subdep of subdeps ) { closure . add ( subdep ) ; } } else { throw new Error ( `Could not find dependency entry for ${ moduleName }` ) ; } } return Array . from ( closure ) . sort ( ) ; } function getDependenciesFromEntry ( depEntry , packageLock ) { const closure = new Set < string > ( ) ; const dependencies = depEntry . dependencies || < { [ key ] : DependencyEntry ; } > { } ; const requires = depEntry . requires || < { [ key ] : string ; } > { } ; for ( const moduleName of Object . keys ( dependencies ) ) { const dependenciesSubdeps = getDependenciesFromEntry ( dependencies [ moduleName ] ! , packageLock ) ; for ( const subdep of dependenciesSubdeps ) { closure . add ( subdep ) ; } } const requiredModules = Object . getOwnPropertyNames ( requires ) . filter ( ( m ) => ! ( m in dependencies ) ) ; const requiresSubdeps = getNodeModulesDependencyClosure ( packageLock , requiredModules ) ; for ( const subdep of requiresSubdeps ) { closure . add ( subdep ) ; } return Array . from ( closure ) . sort ( ) ; } export function getExternalsEntries ( moduleNames ) { const externals = { } ; for ( const moduleName of moduleNames ) { externals [ moduleName ] = `commonjs ${ moduleName }` ; } return externals ; } export function getNodeModuleCopyEntries ( projectRoot , moduleNames ) { const copyEntries = [ ] ; for ( const moduleName of moduleNames ) { copyEntries . push ( { source : path . join ( projectRoot , 'node_modules' , moduleName ) , destination : path . join ( projectRoot , 'dist' , 'node_modules' , moduleName ) } ) ; } return copyEntries ; }