import { eq, gt, maxSatisfying, minSatisfying, satisfies, valid, } from '@renovatebot/ruby-semver'; import { logger } from '../../../logger'; import type { RangeStrategy } from '../../../types/versioning'; import { regEx } from '../../../util/regex'; import type { NewValueConfig, VersioningApi } from '../types'; import { isSingleOperator, isValidOperator } from './operator'; import { ltr, parse as parseRange } from './range'; import { bump, pin, replace, widen } from './strategies'; import { parse as parseVersion } from './version'; export const id = 'ruby'; export const displayName = 'Ruby'; export const urls = [ 'https://guides.rubygems.org/patterns/', 'https://bundler.io/v1.5/gemfile.html', 'https://www.devalot.com/articles/2012/04/gem-versions.html', ]; export const supportsRanges = true; export const supportedRangeStrategies: RangeStrategy[] = [ 'bump', 'widen', 'pin', 'replace', ]; function vtrim(version: T): string | T { if (typeof version === 'string') { return version.replace(regEx(/^v/), '').replace(regEx(/('|")/g), ''); } return version; } const equals = (left: string, right: string): boolean => eq(vtrim(left), vtrim(right)); const getMajor = (version: string): number => parseVersion(vtrim(version)).major; const getMinor = (version: string): number => parseVersion(vtrim(version)).minor; const getPatch = (version: string): number => parseVersion(vtrim(version)).patch; export const isVersion = (version: string): boolean => !!valid(vtrim(version)); const isGreaterThan = (left: string, right: string): boolean => gt(vtrim(left), vtrim(right)); const isLessThanRange = (version: string, range: string): boolean => !!ltr(vtrim(version), vtrim(range)); const isSingleVersion = (range: string): boolean => { const { version, operator } = parseRange(vtrim(range)); return operator ? isVersion(version) && isSingleOperator(operator) : isVersion(version); }; function isStable(version: string): boolean { const v = vtrim(version); return parseVersion(v).prerelease ? false : isVersion(v); } export const isValid = (input: string): boolean => input .split(',') .map((piece) => vtrim(piece.trim())) .every((range) => { const { version, operator } = parseRange(range); return operator ? isVersion(version) && isValidOperator(operator) : isVersion(version); }); export const matches = (version: string, range: string): boolean => satisfies(vtrim(version), vtrim(range)); function getSatisfyingVersion( versions: string[], range: string ): string | null { return maxSatisfying(versions.map(vtrim), vtrim(range)); } function minSatisfyingVersion( versions: string[], range: string ): string | null { return minSatisfying(versions.map(vtrim), vtrim(range)); } const getNewValue = ({ currentValue, rangeStrategy, currentVersion, newVersion, }: NewValueConfig): string | null => { let newValue = null; if (isVersion(currentValue)) { newValue = currentValue.startsWith('v') ? 'v' + newVersion : newVersion; } else if (currentValue.replace(regEx(/^=\s*/), '') === currentVersion) { newValue = currentValue.replace(currentVersion, newVersion); } else { switch (rangeStrategy) { case 'update-lockfile': if (satisfies(newVersion, vtrim(currentValue))) { newValue = vtrim(currentValue); } else { return getNewValue({ currentValue, rangeStrategy: 'replace', currentVersion, newVersion, }); } break; case 'pin': newValue = pin({ to: vtrim(newVersion) }); break; case 'bump': newValue = bump({ range: vtrim(currentValue), to: vtrim(newVersion) }); break; case 'auto': case 'replace': newValue = replace({ range: vtrim(currentValue), to: vtrim(newVersion), }); break; case 'widen': newValue = widen({ range: vtrim(currentValue), to: vtrim(newVersion), }); break; // istanbul ignore next default: logger.warn(`Unsupported strategy ${rangeStrategy}`); } } if (newValue && regEx(/^('|")/).exec(currentValue)) { const delimiter = currentValue[0]; return newValue .split(',') .map((element) => element.replace( regEx(`^(?\\s*)`), `$${delimiter}` ) ) .map( (element) => element.replace(/(?\s*)$/, `${delimiter}$`) // TODO #12875 adds ' at front when re2 is used ) .join(','); } return newValue; }; export const sortVersions = (left: string, right: string): number => gt(vtrim(left), vtrim(right)) ? 1 : -1; export const api: VersioningApi = { equals, getMajor, getMinor, getPatch, isCompatible: isVersion, isGreaterThan, isLessThanRange, isSingleVersion, isStable, isValid, isVersion, matches, getSatisfyingVersion, minSatisfyingVersion, getNewValue, sortVersions, }; export default api;