import fs from "fs" ; import { findPackage , readPackage } from "@rnx-kit/tools-node/package" ; import { resolve } from "path" ; import { promisify } from "util" ; import { createLicenseJSON } from "./output/json" ; import { createLicenseFileContents } from "./output/text" ; import type { License , ModuleNamePathPair , SourceMap , SourceSection , WriteThirdPartyNoticesOptions , } from "./types" ; const modulesRoot = "node_modules/" ; param options export async function writeThirdPartyNotices ( options ) { const readFileAsync = promisify ( fs . readFile ) ; const { additionalText , json , outputFile , preambleText , sourceMapFile } = options ; const sourceMapJson = await readFileAsync ( sourceMapFile , "utf8" ) ; const sourceMap = JSON . parse ( sourceMapJson ) ; const currentPackageId = await getCurrentPackageId ( options . rootPath ) ; const moduleNameToPathMap = extractModuleNameToPathMap ( options , currentPackageId , sourceMap ) ; const licenses = await extractLicenses ( moduleNameToPathMap ) ; const outputText = json ? createLicenseJSON ( licenses ) : createLicenseFileContents ( licenses , preambleText , additionalText ) ; if ( outputFile ) { const writeFileAsync = promisify ( fs . writeFile ) ; await writeFileAsync ( outputFile , outputText , "utf8" ) ; } else { console . log ( outputText ) ; } } export async function getCurrentPackageId ( rootPath ) { const pkgFile = findPackage ( rootPath ) ; if ( pkgFile ) { const manifest = readPackage ( pkgFile ) ; return manifest ?. name ; } return undefined ; } export function normalizePath ( p , currentPackageId ? ) { let result = p . replace ( /webpack:\/\/\//g , "" ) ; if ( currentPackageId ) { result = result . replace ( new RegExp ( `webpack://${ currentPackageId }/` , "g" ) , "" ) ; } return result . replace ( /[\\]+/g , "/" ) ; } export function splitSourcePath ( rootPath , p ) { let nodeModulesPath ; let relativeSourcePath ; const DELEGATED_PREFIX = "delegated " ; if ( p . startsWith ( DELEGATED_PREFIX ) ) { p = p . substring ( DELEGATED_PREFIX . length ) ; } const pnpmRegExp = /(.*?node_modules\/[.].+?\/node_modules\/)(.*)/ ; const pnpmPathComponents = pnpmRegExp . exec ( p ) ; if ( pnpmPathComponents !== null ) { nodeModulesPath = pnpmPathComponents [ 1 ] ; relativeSourcePath = pnpmPathComponents [ 2 ] ; } else { nodeModulesPath = p . substring ( 0 , p . indexOf ( modulesRoot ) + modulesRoot . length ) ; relativeSourcePath = p . substring ( nodeModulesPath . length ) ; } let moduleName ; if ( relativeSourcePath [ 0 ] === "@" ) { moduleName = relativeSourcePath . split ( "/" ) . slice ( 0 , 2 ) . join ( "/" ) ; } else { moduleName = relativeSourcePath . split ( "/" ) [ 0 ] ; } return [ moduleName , resolve ( rootPath , `${ nodeModulesPath }${ moduleName }` ) ] ; } export function parseModule ( options , moduleNameToPath , p ) { const [ moduleName , modulePath ] = splitSourcePath ( options . rootPath , p ) ; const fs = require ( "fs" ) ; if ( ( options . ignoreScopes && options . ignoreScopes . some ( ( scope ) => moduleName . startsWith ( scope + "/" ) ) ) || ( options . ignoreModules && options . ignoreModules . indexOf ( moduleName ) !== - 1 ) || ! fs . existsSync ( modulePath ) ) { return ; } moduleNameToPath . set ( moduleName , modulePath ) ; } export function parseSourceMap ( options , currentPackageId , moduleNameToPath , sourceMap ) { sourceMap . sources . forEach ( ( source ) => { source = normalizePath ( source , currentPackageId ) ; if ( source . includes ( modulesRoot ) ) { parseModule ( options , moduleNameToPath , source ) ; } } ) ; } export function extractModuleNameToPathMap ( options , currentPackageId , sourceMap ) { const moduleNameToPathMap = new Map < string , string > ( ) ; if ( sourceMap . sources ) { parseSourceMap ( options , currentPackageId , moduleNameToPathMap , sourceMap ) ; } if ( sourceMap . sections ) { sourceMap . sections . forEach ( ( section ) => { parseSourceMap ( options , currentPackageId , moduleNameToPathMap , section . map ) ; } ) ; } return moduleNameToPathMap ; } export async function extractLicenses ( moduleNameToPathMap ) { const licenseExtractors = require ( "./extractors" ) ; const moduleNamePathPairs = [ ] ; moduleNameToPathMap . forEach ( ( modulePath , moduleName ) => { if ( moduleName [ 0 ] === "@" ) { const parentModuleName = moduleName . split ( "/" ) [ 0 ] . substring ( 1 ) ; if ( moduleNameToPathMap . has ( parentModuleName ) ) { moduleNameToPathMap . delete ( moduleName ) ; return ; } } moduleNamePathPairs . push ( { name : moduleName , path : modulePath } ) ; } ) ; return await licenseExtractors . nodeModule ( moduleNamePathPairs . sort ( ( { name : lhs } , { name : rhs } ) => lhs < rhs ? - 1 : lhs > rhs ? 1 : 0 ) ) ; }