import is from '@sindresorhus/is' ; import { GitTagsDatasource } from '../../datasource/git-tags' ; import * as datasourcePackagist from '../../datasource/packagist' ; import { logger } from '../../logger' ; import { SkipReason } from '../../types' ; import { readLocalFile } from '../../util/fs' ; import { regEx } from '../../util/regex' ; import { api as semverComposer } from '../../versioning/composer' ; import type { PackageDependency , PackageFile } from '../types' ; import type { ComposerConfig , ComposerLock , ComposerManagerData , Repo , } from './types' ; /**
 * The regUrl is expected to be a base URL. GitLab composer repository installation guide specifies
 * to use a base URL containing packages.json. Composer still works in this scenario by determining
 * whether to add / remove packages.json from the URL.
 *
 * See https://github.com/composer/composer/blob/750a92b4b7aecda0e5b2f9b963f1cb1421900675/src/Composer/Repository/ComposerRepository.php#L815
 */ function transformRegUrl ( url ) { return url . replace ( regEx ( /(\/packages\.json)$/ ) , '' ) ; } /**
 * Parse the repositories field from a composer.json
 *
 * Entries with type vcs or git will be added to repositories,
 * other entries will be added to registryUrls
 */ function parseRepositories ( repoJson , repositories , registryUrls ) { try { let packagist = true ; Object . entries ( repoJson ) . forEach ( ( [ key , repo ] ) => { if ( is . object ( repo ) ) { const name = is . array ( repoJson ) ? repo . name : key ; switch ( repo . type ) { case 'vcs' : case 'git' : repositories [ name ] = repo ; break ; case 'composer' : registryUrls . push ( transformRegUrl ( repo . url ) ) ; break ; case 'package' : logger . debug ( { url : repo . url } , 'type package is not supported yet' ) ; } if ( repo . packagist === false || repo [ 'packagist.org' ] === false ) { packagist = false ; } } else if ( [ 'packagist' , 'packagist.org' ] . includes ( key ) && repo === false ) { packagist = false ; } } ) ; if ( packagist ) { registryUrls . push ( 'https://packagist.org' ) ; } else { logger . debug ( 'Disabling packagist.org' ) ; } } catch ( e ) { logger . debug ( { repositories : repoJson } , 'Error parsing composer.json repositories config' ) ; } } export async function extractPackageFile ( content , fileName ) { logger . trace ( `composer.extractPackageFile(${ fileName })` ) ; let composerJson ; try { composerJson = JSON . parse ( content ) ; } catch ( err ) { logger . debug ( { fileName } , 'Invalid JSON' ) ; return null ; } const repositories = { } ; const registryUrls = [ ] ; const res = { deps : [ ] } ; const lockfilePath = fileName . replace ( regEx ( /\.json$/ ) , '.lock' ) ; const lockContents = await readLocalFile ( lockfilePath , 'utf8' ) ; let lockParsed ; if ( lockContents ) { logger . debug ( { packageFile : fileName } , 'Found composer lock file' ) ; res . lockFiles = [ lockfilePath ] ; try { lockParsed = JSON . parse ( lockContents ) as ComposerLock ; } catch ( err ) { logger . warn ( { err } , 'Error processing composer.lock' ) ; } } if ( composerJson . repositories ) { parseRepositories ( composerJson . repositories , repositories , registryUrls ) ; } if ( registryUrls . length !== 0 ) { res . registryUrls = registryUrls ; } const deps = [ ] ; const depTypes = [ 'require' , 'require-dev' ] ; for ( const depType of depTypes ) { if ( composerJson [ depType ] ) { try { for ( const [ depName , version ] of Object . entries ( composerJson [ depType ] as Record < string , string > ) ) { const currentValue = version . trim ( ) ; let datasource = datasourcePackagist . id ; let lookupName = depName ; if ( repositories [ depName ] ) { switch ( repositories [ depName ] . type ) { case 'vcs' : case 'git' : datasource = GitTagsDatasource . id ; lookupName = repositories [ depName ] . url ; break ; } } const dep = { depType , depName , currentValue , datasource , } ; if ( depName !== lookupName ) { dep . lookupName = lookupName ; } if ( ! depName . includes ( '/' ) ) { dep . skipReason = SkipReason . Unsupported ; } if ( lockParsed ) { const lockField = depType === 'require' ? 'packages' : 'packages-dev' ; const lockedDep = lockParsed [ lockField ] ?. find ( ( item ) => item . name === dep . depName ) ; if ( lockedDep && semverComposer . isVersion ( lockedDep . version ) ) { dep . lockedVersion = lockedDep . version . replace ( regEx ( /^v/i ) , '' ) ; } } deps . push ( dep ) ; } } catch ( err ) { logger . debug ( { fileName , depType , err } , 'Error parsing composer.json' ) ; return null ; } } } if ( ! deps . length ) { return null ; } res . deps = deps ; if ( is . string ( composerJson . type ) ) { const managerData = { composerJsonType : composerJson . type , } ; res . managerData = managerData ; } return res ; }