import { UsageError } from 'clipanion' ; import fs from 'fs' ; import path from 'path' ; import semver from 'semver' ; import defaultConfig from '../config.json' ; import * as folderUtils from './folderUtils' ; import * as corepackUtils from './corepackUtils' ; import * as semverUtils from './semverUtils' ; import { SupportedPackageManagers , SupportedPackageManagerSet } from './types' ; import { Config , Descriptor , Locator } from './types' ; export class Engine { constructor ( public config = defaultConfig as Config ) { } getPackageManagerFor ( binaryName ) { for ( const packageManager of SupportedPackageManagerSet ) { for ( const rangeDefinition of Object . values ( this . config . definitions [ packageManager ] ! . ranges ) ) { const bins = Array . isArray ( rangeDefinition . bin ) ? rangeDefinition . bin : Object . keys ( rangeDefinition . bin ) ; if ( bins . includes ( binaryName ) ) { return packageManager ; } } } return null ; } getBinariesFor ( name ) { const binNames = new Set < string > ( ) ; for ( const rangeDefinition of Object . values ( this . config . definitions [ name ] ! . ranges ) ) { const bins = Array . isArray ( rangeDefinition . bin ) ? rangeDefinition . bin : Object . keys ( rangeDefinition . bin ) ; for ( const name of bins ) { binNames . add ( name ) ; } } return binNames ; } async getDefaultDescriptors ( ) { const locators = [ ] ; for ( const name of SupportedPackageManagerSet as Set < SupportedPackageManagers > ) locators . push ( { name , range : await this . getDefaultVersion ( name ) } ) ; return locators ; } async getDefaultVersion ( packageManager ) { const definition = this . config . definitions [ packageManager ] ; if ( typeof definition === `undefined` ) throw new UsageError ( `This package manager (${ packageManager }) isn't supported by this corepack build` ) ; let lastKnownGood ; try { lastKnownGood = JSON . parse ( await fs . promises . readFile ( this . getLastKnownGoodFile ( ) , `utf8` ) ) ; } catch { } if ( typeof lastKnownGood !== `object` || lastKnownGood === null ) return definition . default ; if ( ! Object . prototype . hasOwnProperty . call ( lastKnownGood , packageManager ) ) return definition . default ; const override = ( lastKnownGood as any ) [ packageManager ] ; if ( typeof override !== `string` ) return definition . default ; return override ; } async activatePackageManager ( locator ) { const lastKnownGoodFile = this . getLastKnownGoodFile ( ) ; let lastKnownGood ; try { lastKnownGood = JSON . parse ( await fs . promises . readFile ( lastKnownGoodFile , `utf8` ) ) ; } catch { } if ( typeof lastKnownGood !== `object` || lastKnownGood === null ) lastKnownGood = { } ; lastKnownGood [ locator . name ] = locator . reference ; await fs . promises . mkdir ( path . dirname ( lastKnownGoodFile ) , { recursive : true } ) ; await fs . promises . writeFile ( lastKnownGoodFile , `${ JSON . stringify ( lastKnownGood , null , 2 ) }\n` ) ; } async ensurePackageManager ( locator ) { const definition = this . config . definitions [ locator . name ] ; if ( typeof definition === `undefined` ) throw new UsageError ( `This package manager (${ locator . name }) isn't supported by this corepack build` ) ; const ranges = Object . keys ( definition . ranges ) . reverse ( ) ; const range = ranges . find ( range => semverUtils . satisfiesWithPrereleases ( locator . reference , range ) ) ; if ( typeof range === `undefined` ) throw new Error ( `Assertion failed: Specified resolution (${ locator . reference }) isn't supported by any of ${ ranges . join ( `, ` ) }` ) ; const installedLocation = await corepackUtils . installVersion ( folderUtils . getInstallFolder ( ) , locator , { spec : definition . ranges [ range ] , } ) ; return { location : installedLocation , spec : definition . ranges [ range ] , } ; } async resolveDescriptor ( descriptor , { allowTags = false , useCache = true } = { } ) { const definition = this . config . definitions [ descriptor . name ] ; if ( typeof definition === `undefined` ) throw new UsageError ( `This package manager (${ descriptor . name }) isn't supported by this corepack build` ) ; let finalDescriptor = descriptor ; if ( descriptor . range . match ( /^[a-z-]+$/ ) ) { if ( ! allowTags ) throw new UsageError ( `Packages managers can't be referended via tags in this context` ) ; const ranges = Object . keys ( definition . ranges ) ; const tagRange = ranges [ ranges . length - 1 ] ; const tags = await corepackUtils . fetchAvailableTags ( definition . ranges [ tagRange ] . registry ) ; if ( ! Object . prototype . hasOwnProperty . call ( tags , descriptor . range ) ) throw new UsageError ( `Tag not found (${ descriptor . range })` ) ; finalDescriptor = { name : descriptor . name , range : tags [ descriptor . range ] , } ; } const cachedVersion = await corepackUtils . findInstalledVersion ( folderUtils . getInstallFolder ( ) , finalDescriptor ) ; if ( cachedVersion !== null && useCache ) return { name : finalDescriptor . name , reference : cachedVersion } ; const candidateRangeDefinitions = Object . keys ( definition . ranges ) . filter ( range => { return semverUtils . satisfiesWithPrereleases ( finalDescriptor . range , range ) ; } ) ; const tagResolutions = await Promise . all ( candidateRangeDefinitions . map ( async ( range ) => { return [ range , await corepackUtils . fetchAvailableVersions ( definition . ranges [ range ] . registry ) ] as const ; } ) ) ; const resolutionMap = new Map ( ) ; for ( const [ range , resolutions ] of tagResolutions ) for ( const entry of resolutions ) resolutionMap . set ( entry , range ) ; const candidates = [ ... resolutionMap . keys ( ) ] ; const maxSatisfying = semver . maxSatisfying ( candidates , finalDescriptor . range ) ; if ( maxSatisfying === null ) return null ; return { name : finalDescriptor . name , reference : maxSatisfying } ; } private getLastKnownGoodFile ( ) { return path . join ( folderUtils . getInstallFolder ( ) , `lastKnownGood.json` ) ; } }