import semver from 'semver' ; import { parseRange } from 'semver-utils' ; import { logger } from '../../logger' ; import { regEx } from '../../util/regex' ; import { api as npm } from '../npm' ; import type { NewValueConfig , VersioningApi } from '../types' ; export const id = 'composer' ; export const displayName = 'Composer' ; export const urls = [ 'https://getcomposer.org/doc/articles/versions.md' , 'https://packagist.org/packages/composer/semver' , 'https://madewithlove.be/tilde-and-caret-constraints/' , 'https://semver.mwl.be' , ] ; export const supportsRanges = true ; export const supportedRangeStrategies = [ 'bump' , 'extend' , 'pin' , 'replace' ] ; function getVersionParts ( input ) { const versionParts = input . split ( '-' ) ; if ( versionParts . length === 1 ) { return [ input , '' ] ; } return [ versionParts [ 0 ] , '-' + versionParts [ 1 ] ] ; } function padZeroes ( input ) { const [ output , stability ] = getVersionParts ( input ) ; const sections = output . split ( '.' ) ; while ( sections . length < 3 ) { sections . push ( '0' ) ; } return sections . join ( '.' ) + stability ; } function convertStabilityModifier ( input ) { const versionParts = input . split ( '@' ) ; if ( versionParts . length === 1 ) { return input ; } const stability = versionParts [ 1 ] . replace ( regEx ( /(?:^|\s)(beta|alpha|rc)([1-9][0-9]*)(?: |$)/gi ) , '$1.$2' ) ; return padZeroes ( versionParts [ 0 ] ) + '-' + stability ; } function normalizeVersion ( input ) { let output = input ; output = output . replace ( regEx ( /(^|>|>=|\^|~)v/i ) , '$1' ) ; return convertStabilityModifier ( output ) ; } function composer2npm ( input ) { const cleanInput = normalizeVersion ( input ) ; if ( npm . isVersion ( cleanInput ) ) { return cleanInput ; } if ( npm . isVersion ( padZeroes ( cleanInput ) ) ) { return padZeroes ( cleanInput ) ; } const [ versionId , stability ] = getVersionParts ( cleanInput ) ; let output = versionId ; output = output . replace ( regEx ( /(?:^|\s)~([1-9][0-9]*(?:\.[0-9]*)?)(?: |$)/g ) , '^$1' ) ; output = output . replace ( regEx ( /(?:^|\s)~(0\.[1-9][0-9]*)(?: |$)/g ) , '>=$1 <1' ) ; return output + stability ; } const equals = ( a , b ) : boolean => npm . equals ( composer2npm ( a ) , composer2npm ( b ) ) ; const getMajor = ( version ) : number => npm . getMajor ( semver . coerce ( composer2npm ( version ) ) ) ; const getMinor = ( version ) : number => npm . getMinor ( semver . coerce ( composer2npm ( version ) ) ) ; const getPatch = ( version ) : number => npm . getPatch ( semver . coerce ( composer2npm ( version ) ) ) ; const isGreaterThan = ( a , b ) : boolean => npm . isGreaterThan ( composer2npm ( a ) , composer2npm ( b ) ) ; const isLessThanRange = ( version , range ) : boolean => npm . isLessThanRange ( composer2npm ( version ) , composer2npm ( range ) ) ; const isSingleVersion = ( input ) : boolean => ! ! input && npm . isSingleVersion ( composer2npm ( input ) ) ; const isStable = ( version ) : boolean => version && npm . isStable ( composer2npm ( version ) ) ; export const isValid = ( input ) : boolean => ! ! input && npm . isValid ( composer2npm ( input ) ) ; export const isVersion = ( input ) : boolean => ! ! input && npm . isVersion ( composer2npm ( input ) ) ; const matches = ( version , range ) : boolean => npm . matches ( composer2npm ( version ) , composer2npm ( range ) ) ; const getSatisfyingVersion = ( versions , range ) : string => npm . getSatisfyingVersion ( versions . map ( composer2npm ) , composer2npm ( range ) ) ; const minSatisfyingVersion = ( versions , range ) : string => npm . minSatisfyingVersion ( versions . map ( composer2npm ) , composer2npm ( range ) ) ; function getNewValue ( { currentValue , rangeStrategy , currentVersion , newVersion , } ) { if ( rangeStrategy === 'pin' ) { return newVersion ; } if ( rangeStrategy === 'update-lockfile' ) { if ( matches ( newVersion , currentValue ) ) { return currentValue ; } return getNewValue ( { currentValue , rangeStrategy : 'replace' , currentVersion , newVersion , } ) ; } const toMajor = getMajor ( newVersion ) ; const toMinor = getMinor ( newVersion ) ; let newValue ; if ( isVersion ( currentValue ) ) { newValue = newVersion ; } else if ( regEx ( /^[~^](0\.[1-9][0-9]*)$/ ) . test ( currentValue ) ) { const operator = currentValue . substr ( 0 , 1 ) ; if ( toMajor === 0 ) { newValue = `${ operator }0.${ toMinor }` ; } else { newValue = `${ operator }${ toMajor }.0` ; } } else if ( regEx ( /^[~^]([0-9]*)$/ ) . test ( currentValue ) ) { const operator = currentValue . substr ( 0 , 1 ) ; newValue = `${ operator }${ toMajor }` ; } else if ( regEx ( /^[~^]([0-9]*(?:\.[0-9]*)?)$/ ) . test ( currentValue ) ) { const operator = currentValue . substr ( 0 , 1 ) ; if ( currentVersion && toMajor > getMajor ( currentVersion ) ) { newValue = `${ operator }${ toMajor }.0` ; } else { newValue = `${ operator }${ toMajor }.${ toMinor }` ; } } else if ( npm . isVersion ( padZeroes ( normalizeVersion ( newVersion ) ) ) && npm . isValid ( normalizeVersion ( currentValue ) ) && composer2npm ( currentValue ) === normalizeVersion ( currentValue ) ) { newValue = npm . getNewValue ( { currentValue : normalizeVersion ( currentValue ) , rangeStrategy , currentVersion : normalizeVersion ( currentVersion ) , newVersion : padZeroes ( normalizeVersion ( newVersion ) ) , } ) ; } if ( rangeStrategy === 'widen' && matches ( newVersion , currentValue ) ) { newValue = currentValue ; } else { const hasOr = currentValue . includes ( ' || ' ) ; if ( hasOr || rangeStrategy === 'widen' ) { const lastValue = hasOr ? currentValue . split ( '||' ) . pop ( ) . trim ( ) : currentValue ; const replacementValue = getNewValue ( { currentValue : lastValue , rangeStrategy : 'replace' , currentVersion , newVersion , } ) ; if ( rangeStrategy === 'replace' ) { newValue = replacementValue ; } else { const parsedRange = parseRange ( replacementValue ) ; const element = parsedRange [ parsedRange . length - 1 ] ; if ( element . operator ?. startsWith ( '<' ) ) { const splitCurrent = currentValue . split ( element . operator ) ; splitCurrent . pop ( ) ; newValue = splitCurrent . join ( element . operator ) + replacementValue ; } else { newValue = currentValue + ' || ' + replacementValue ; } } } } if ( ! newValue ) { logger . warn ( { currentValue , rangeStrategy , currentVersion , newVersion } , 'Unsupported composer value' ) ; newValue = newVersion ; } if ( currentValue . split ( '.' ) [ 0 ] . includes ( 'v' ) ) { newValue = newValue . replace ( regEx ( /([0-9])/ ) , 'v$1' ) ; } if ( currentValue . includes ( '@' ) ) { newValue += '@' + currentValue . split ( '@' ) [ 1 ] ; } return newValue ; } function sortVersions ( a , b ) { return npm . sortVersions ( composer2npm ( a ) , composer2npm ( b ) ) ; } function isCompatible ( version ) { return isVersion ( version ) ; } export const api = { equals , getMajor , getMinor , getPatch , isCompatible , isGreaterThan , isLessThanRange , isSingleVersion , isStable , isValid , isVersion , matches , getSatisfyingVersion , minSatisfyingVersion , getNewValue , sortVersions , } ; export default api ;