import is from '@sindresorhus/is' ; import moo from 'moo' ; import pAll from 'p-all' ; import { logger } from '../../logger' ; import * as packageCache from '../../util/cache/package' ; import { regEx } from '../../util/regex' ; import type { GetReleasesConfig , Release , ReleaseResult } from '../types' ; import { GoproxyFallback , getSourceUrl , http } from './common' ; import { getDatasource } from './get-datasource' ; import * as direct from './releases-direct' ; import type { GoproxyItem , VersionInfo } from './types' ; const parsedGoproxy = { } ; example see https export function parseGoproxy ( input = process . env . GOPROXY ) { if ( ! is . string ( input ) ) { return [ ] ; } if ( parsedGoproxy [ input ] ) { return parsedGoproxy [ input ] ; } const result = input . split ( regEx ( /([^,|]*(?:,|\|))/ ) ) . filter ( Boolean ) . map ( ( s ) => s . split ( /(?=,|\|)/ ) ) . map ( ( [ url , separator ] ) => ( { url , fallback : separator === ',' ? GoproxyFallback . WhenNotFoundOrGone : GoproxyFallback . Always , } ) ) ; parsedGoproxy [ input ] = result ; return result ; } const lexer = moo . states ( { main : { separator : { match : /\s*?,\s*?/ , value : ( _ ) => '|' , } , asterisk : { match : '*' , value : ( _ ) => '[^\\/]*' , } , qmark : { match : '?' , value : ( _ ) => '[^\\/]' , } , characterRangeOpen : { match : '[' , push : 'characterRange' , value : ( _ ) => '[' , } , trailingSlash : { match : /\/$/ , value : ( _ ) => '' , } , char : { match : /[^*?\\[\n]/ , value : ( s ) => s . replace ( regEx ( '\\.' , 'g' ) , '\\.' ) , } , escapedChar : { match : /\\./ , value : ( s ) => s . slice ( 1 ) , } , } , characterRange : { char : /[^\\\]\n]/ , escapedChar : { match : /\\./ , value : ( s ) => s . slice ( 1 ) , } , characterRangeEnd : { match : ']' , pop : 1 , } , } , } ) ; const parsedNoproxy = { } ; export function parseNoproxy ( input = process . env . GONOPROXY || process . env . GOPRIVATE ) { if ( ! is . string ( input ) ) { return null ; } if ( parsedNoproxy [ input ] !== undefined ) { return parsedNoproxy [ input ] ; } lexer . reset ( input ) ; const noproxyPattern = [ ... lexer ] . map ( ( { value } ) => value ) . join ( '' ) ; const result = noproxyPattern ? regEx ( `^(?:${ noproxyPattern })(?:/.*)?$` ) : null ; parsedNoproxy [ input ] = result ; return result ; } see https export function encodeCase ( input ) { return input . replace ( regEx ( /([A-Z])/g ) , ( x ) => `!${ x . toLowerCase ( ) }` ) ; } export async function listVersions ( baseUrl , lookupName ) { const url = `${ baseUrl }/${ encodeCase ( lookupName ) }/@v/list` ; const { body } = await http . get ( url ) ; return body . split ( regEx ( /\s+/ ) ) . filter ( Boolean ) . filter ( ( x ) => x . indexOf ( '+' ) === - 1 ) ; } export async function versionInfo ( baseUrl , lookupName , version ) { const url = `${ baseUrl }/${ encodeCase ( lookupName ) }/@v/${ version }.info` ; const res = await http . getJson < VersionInfo > ( url ) ; const result = { version : res . body . Version , } ; if ( res . body . Time ) { result . releaseTimestamp = res . body . Time ; } return result ; } export async function getReleases ( config ) { const { lookupName } = config ; logger . trace ( `goproxy.getReleases(${ lookupName })` ) ; const goproxy = process . env . GOPROXY ; const proxyList = parseGoproxy ( goproxy ) ; const noproxy = parseNoproxy ( ) ; const cacheNamespaces = 'datasource-go-proxy' ; const cacheKey = `${ lookupName }@@${ goproxy }@@${ noproxy ?. toString ( ) }` ; const cacheMinutes = 60 ; const cachedResult = await packageCache . get < ReleaseResult | null > ( cacheNamespaces , cacheKey ) ; if ( cachedResult || cachedResult === null ) { return cachedResult ; } let result = null ; if ( noproxy ?. test ( lookupName ) ) { logger . debug ( `Fetching ${ lookupName } via GONOPROXY match` ) ; result = await direct . getReleases ( config ) ; await packageCache . set ( cacheNamespaces , cacheKey , result , cacheMinutes ) ; return result ; } for ( const { url , fallback } of proxyList ) { try { if ( url === 'off' ) { break ; } else if ( url === 'direct' ) { result = await direct . getReleases ( config ) ; break ; } const versions = await listVersions ( url , lookupName ) ; const queue = versions . map ( ( version ) => async ( ) : Promise < Release > => { try { return await versionInfo ( url , lookupName , version ) ; } catch ( err ) { logger . trace ( { err } , `Can't obtain data from ${ url }` ) ; return { version } ; } } ) ; const releases = await pAll ( queue , { concurrency : 5 } ) ; if ( releases . length ) { const datasource = await getDatasource ( lookupName ) ; const sourceUrl = getSourceUrl ( datasource ) ; result = { releases , sourceUrl } ; break ; } } catch ( err ) { const statusCode = err ?. response ?. statusCode ; const canFallback = fallback === GoproxyFallback . Always ? true : statusCode === 404 || statusCode === 410 ; const msg = canFallback ? 'Goproxy error: trying next URL provided with GOPROXY' : 'Goproxy error: skipping other URLs provided with GOPROXY' ; logger . debug ( { err } , msg ) ; if ( ! canFallback ) { break ; } } } await packageCache . set ( cacheNamespaces , cacheKey , result , cacheMinutes ) ; return result ; }