import { ITypeSyncer , IPackageJSONService , ITypeDefinitionSource , ITypeDefinition , IPackageFile , ISyncOptions , IDependenciesSection , IPackageVersion , ISyncResult , ISyncedFile , IPackageSource , IPackageInfo , ISyncedTypeDefinition , IConfigService , IDependencySection , ICLIArguments , } from './types' ; import { filterMap , mergeObjects , typed , orderObject , uniq , flatten , memoizeAsync , ensureWorkspacesArray , untyped , } from './util' ; import { IGlobber } from './globber' ; import { satisfies } from 'semver' ; param packageJSONservice param typeDefinitionSource export function createTypeSyncer ( packageJSONService , typeDefinitionSource , packageSource , configService , globber ) { const fetchPackageInfo = memoizeAsync ( packageSource . fetch ) ; return { sync , } ; /**
     * Syncs typings in the specified package.json.
     */ async function sync ( filePath , flags ) { const dryRun = ! ! flags . dry ; const [ file , allTypings , syncOpts ] = await Promise . all ( [ packageJSONService . readPackageFile ( filePath ) , typeDefinitionSource . fetch ( ) , configService . readConfig ( filePath , flags ) , ] ) ; const subPackages = await Promise . all ( [ ... ensureWorkspacesArray ( file . packages ) , ... ensureWorkspacesArray ( file . workspaces ) , ] . map ( globber . globPackageFiles ) ) . then ( flatten ) . then ( uniq ) ; const syncedFiles = await Promise . all ( [ syncFile ( filePath , file , allTypings , syncOpts , dryRun ) , ... subPackages . map ( ( p ) => syncFile ( p , null , allTypings , syncOpts , dryRun ) ) , ] ) ; return { syncedFiles , } ; } param filePath param file param allTypings param opts async function syncFile ( filePath , file , allTypings , opts , dryRun ) { const { ignoreDeps , ignorePackages } = opts ; const packageFile = file || ( await packageJSONService . readPackageFile ( filePath ) ) ; const allPackages = flatten ( filterMap ( Object . values ( IDependencySection ) , ( dep ) => { if ( ignoreDeps ?. includes ( dep ) ) { return false ; } const section = getDependenciesBySection ( packageFile , dep ) ; return getPackagesFromSection ( section , ignorePackages ) ; } ) ) ; const allPackageNames = uniq ( allPackages . map ( ( p ) => p . name ) ) ; const newTypings = filterNewTypings ( allPackageNames , allTypings ) ; const used = [ ] ; const devDepsToAdd = await Promise . all ( newTypings . map ( async ( t ) => { const typePackageInfoPromise = fetchPackageInfo ( typed ( t . typingsName ) ) ; const codePackageInfo = await fetchPackageInfo ( t . codePackageName ) ; const codePackage = allPackages . find ( ( p ) => p . name === t . codePackageName ) ! ; const closestMatchingCodeVersion = getClosestMatchingVersion ( codePackageInfo , codePackage . version ) ; if ( closestMatchingCodeVersion . containsInternalTypings ) { return { } ; } const typePackageInfo = await typePackageInfoPromise ; const closestMatchingTypingsVersion = getClosestMatchingVersion ( typePackageInfo , codePackage . version ) ; const version = closestMatchingTypingsVersion . version ; const semverRangeSpecifier = getSemverRangeSpecifier ( codePackage . version ) ; used . push ( t ) ; return { [ typed ( t . typingsName ) ] : semverRangeSpecifier + version , } ; } ) ) . then ( mergeObjects ) ; const devDeps = packageFile . devDependencies || { } ; const unused = getUnusedTypings ( allPackageNames , devDeps , allTypings ) ; if ( ! dryRun ) { await packageJSONService . writePackageFile ( filePath , { ... packageFile , devDependencies : orderObject ( { ... devDepsToAdd , ... removeUnusedTypings ( devDeps , unused ) , } ) , } as IPackageFile ) ; } return { filePath , newTypings : used , removedTypings : unused , package : packageFile , } ; } } param allPackageNames param devDependencies function removeUnusedTypings ( devDependencies , unusedTypings ) { const result = { } ; for ( let packageName in devDependencies ) { const version = devDependencies [ packageName ] ; if ( unusedTypings . some ( ( t ) => t . typingsPackageName === packageName ) ) { continue ; } result [ packageName ] = version ; } return result ; } param allPackageNames param devDependencies function getUnusedTypings ( allPackageNames , devDependencies , allTypings ) { const result = [ ] ; for ( let packageName in devDependencies ) { if ( packageName . startsWith ( '@types/' ) ) { const codePackageName = untyped ( packageName ) ; const hasCodePackageForTyping = allPackageNames . some ( ( p ) => p === codePackageName ) ; if ( ! hasCodePackageForTyping ) { const typingsNameForCodePackage = getTypingsName ( codePackageName ) ; const typeDef = allTypings . find ( ( t ) => t . typingsName === typingsNameForCodePackage ) ; if ( typeDef && ! typeDef . isGlobal ) { result . push ( { codePackageName , typingsPackageName : packageName , ... typeDef , } ) ; } } } } return result ; } param packageInfo param version function getClosestMatchingVersion ( packageInfo , version ) { return ( packageInfo . versions . find ( ( v ) => satisfies ( v . version , version ) ) || packageInfo . versions [ 0 ] ) ; } param allPackageNames param allTypings function filterNewTypings ( allPackageNames , allTypings ) { const existingTypings = allPackageNames . filter ( ( x ) => x . startsWith ( '@types/' ) ) ; return filterMap ( allPackageNames , ( p ) => { let typingsName = getTypingsName ( p ) ; const typingsForPackage = allTypings . find ( ( x ) => x . typingsName === typingsName ) ; if ( ! typingsForPackage ) { return false ; } const fullTypingsPackage = typed ( typingsForPackage . typingsName ) ; const alreadyHasTyping = existingTypings . some ( ( t ) => t === fullTypingsPackage ) ; if ( alreadyHasTyping ) { return false ; } return { ... typingsForPackage , codePackageName : p , } ; } ) ; } param packageName function getTypingsName ( packageName ) { const scopeInfo = getPackageScope ( packageName ) ; let typingsName = packageName ; if ( scopeInfo && scopeInfo [ 0 ] !== 'types' ) { typingsName = `${ scopeInfo [ 0 ] }__${ scopeInfo [ 1 ] }` ; } return typingsName ; } param packageName function getPackageScope ( packageName ) { const EXPR = /^\@([^\/]+)\/(.*)$/i ; const matches = EXPR . exec ( packageName ) ; if ( ! matches ) { return null ; } return [ matches [ 1 ] , matches [ 2 ] ] ; } param section param ignorePackages function getPackagesFromSection ( section , ignorePackages ? ) { return filterMap ( Object . keys ( section ) , ( name ) => { if ( ignorePackages ?. includes ( name ) ) { return false ; } return { name , version : section [ name ] } ; } ) ; } param file param section function getDependenciesBySection ( file , section ) { const dependenciesSection = ( ( ) => { switch ( section ) { case IDependencySection . deps : return file . dependencies ; case IDependencySection . dev : return file . devDependencies ; case IDependencySection . optional : return file . optionalDependencies ; case IDependencySection . peer : return file . peerDependencies ; } } ) ( ) ; return dependenciesSection ?? { } ; } const CARET = '^' . charCodeAt ( 0 ) ; const TILDE = '~' . charCodeAt ( 0 ) ; param version function getSemverRangeSpecifier ( version ) { if ( version . charCodeAt ( 0 ) === CARET ) { return '^' ; } if ( version . charCodeAt ( 0 ) === TILDE ) { return '~' ; } return '' ; }