import { logger } from '../../logger' ; import { ExternalHostError } from '../../types/errors/external-host-error' ; import { getElapsedMinutes } from '../../util/date' ; import { Datasource } from '../datasource' ; import type { GetReleasesConfig , ReleaseResult } from '../types' ; let lastSync = new Date ( '2000-01-01' ) ; let packageReleases = Object . create ( null ) ; let contentLength = 0 ; export function resetCache ( ) { lastSync = new Date ( '2000-01-01' ) ; packageReleases = Object . create ( null ) ; contentLength = 0 ; } export class RubyGemsOrgDatasource extends Datasource { constructor ( override readonly id ) { super ( id ) ; } async getReleases ( { lookupName , } ) { logger . debug ( `getRubygemsOrgDependency(${ lookupName })` ) ; await this . syncVersions ( ) ; if ( ! packageReleases [ lookupName ] ) { return null ; } const dep = { releases : packageReleases [ lookupName ] . map ( ( version ) => ( { version , } ) ) , } ; return dep ; } /**
     * https://bugs.chromium.org/p/v8/issues/detail?id=2869
     */ private static copystr ( x ) { return ( ' ' + x ) . slice ( 1 ) ; } async updateRubyGemsVersions ( ) { const url = 'https://rubygems.org/versions' ; const options = { headers : { 'accept-encoding' : 'identity' , range : `bytes=${ contentLength }-` , } , } ; let newLines ; try { logger . debug ( 'Rubygems: Fetching rubygems.org versions' ) ; const startTime = Date . now ( ) ; newLines = ( await this . http . get ( url , options ) ) . body ; const durationMs = Math . round ( Date . now ( ) - startTime ) ; logger . debug ( { durationMs } , 'Rubygems: Fetched rubygems.org versions' ) ; } catch ( err ) { if ( err . statusCode !== 416 ) { contentLength = 0 ; packageReleases = Object . create ( null ) ; throw new ExternalHostError ( new Error ( 'Rubygems fetch error - need to reset cache' ) ) ; } logger . debug ( 'Rubygems: No update' ) ; lastSync = new Date ( ) ; return ; } for ( const line of newLines . split ( '\n' ) ) { RubyGemsOrgDatasource . processLine ( line ) ; } lastSync = new Date ( ) ; } private static processLine ( line ) { let split ; let pkg ; let versions ; try { const l = line . trim ( ) ; if ( ! l . length || l . startsWith ( 'created_at:' ) || l === '---' ) { return ; } split = l . split ( ' ' ) ; [ pkg , versions ] = split ; pkg = RubyGemsOrgDatasource . copystr ( pkg ) ; packageReleases [ pkg ] = packageReleases [ pkg ] || [ ] ; const lineVersions = versions . split ( ',' ) . map ( ( version ) => version . trim ( ) ) ; for ( const lineVersion of lineVersions ) { if ( lineVersion . startsWith ( '-' ) ) { const deletedVersion = lineVersion . slice ( 1 ) ; logger . trace ( { pkg , deletedVersion } , 'Rubygems: Deleting version' ) ; packageReleases [ pkg ] = packageReleases [ pkg ] . filter ( ( version ) => version !== deletedVersion ) ; } else { packageReleases [ pkg ] . push ( RubyGemsOrgDatasource . copystr ( lineVersion ) ) ; } } } catch ( err ) { logger . warn ( { err , line , split , pkg , versions } , 'Rubygems line parsing error' ) ; } } private static isDataStale ( ) { return getElapsedMinutes ( lastSync ) >= 5 ; } updateRubyGemsVersionsPromise : Promise < void > | undefined ; async syncVersions ( ) { if ( RubyGemsOrgDatasource . isDataStale ( ) ) { this . updateRubyGemsVersionsPromise = this . updateRubyGemsVersionsPromise || this . updateRubyGemsVersions ( ) ; await this . updateRubyGemsVersionsPromise ; this . updateRubyGemsVersionsPromise = null ; } } }