@@ -26,18 +26,31 @@ type ParserIncludesInitOptions = {
2626} ;
2727
2828type ParsedComponent = {
29+ _cache : {
30+ version : string | null | undefined ;
31+ effectiveRef : string | undefined ;
32+ sha : string | undefined ;
33+ } ;
34+ gitData : GitData ;
2935 domain : string ;
3036 port : string ;
3137 projectPath : string ;
3238 componentPath : string ;
33- effectiveRef : string ;
3439 name : string ;
3540 reference : string ;
36- version : string | undefined ;
41+ version : string | null ;
42+ effectiveRef : string ;
3743 sha : string ;
3844 isLocal : boolean ;
3945} ;
4046
47+ type GitRemoteInfoContext = {
48+ gitData : GitData ;
49+ domain : string ;
50+ port : string ;
51+ projectPath : string ;
52+ } ;
53+
4154export class ParserIncludes {
4255 private static count : number = 0 ;
4356
@@ -100,7 +113,7 @@ export class ParserIncludes {
100113 } else if ( value [ "remote" ] ) {
101114 promises . push ( this . downloadIncludeRemote ( cwd , stateDir , value [ "remote" ] , fetchIncludes , writeStreams ) ) ;
102115 } else if ( value [ "component" ] ) {
103- const component = this . parseIncludeComponent ( value [ "component" ] , gitData , opts ) ;
116+ const component = this . parseIncludeComponent ( value [ "component" ] , gitData ) ;
104117 componentParseCache . set ( index , component ) ;
105118 if ( ! component . isLocal )
106119 {
@@ -238,40 +251,74 @@ export class ParserIncludes {
238251 } ;
239252 }
240253
241- static parseIncludeComponent ( component : string , gitData : GitData , opts : ParserIncludesInitOptions ) : ParsedComponent {
254+ static parseIncludeComponent ( component : string , gitData : GitData ) : ParsedComponent {
242255 assert ( ! component . includes ( "://" ) , `This GitLab CI configuration is invalid: component: \`${ component } \` should not contain protocol` ) ;
243256 const pattern = / (?< domain > [ ^ / : \s ] + ) ( : (?< port > \d + ) ) ? \/ (?< projectPath > .+ ) \/ (?< componentName > [ ^ @ ] + ) @ (?< ref > .+ ) / ; // https://regexr.com/7v7hm
244257 const gitRemoteMatch = pattern . exec ( component ) ;
245-
246258 if ( gitRemoteMatch ?. groups == null ) throw new Error ( `This is a bug, please create a github issue if this is something you're expecting to work. input: ${ component } ` ) ;
247-
248- const { domain, projectPath, port} = gitRemoteMatch . groups ;
249- const reference = gitRemoteMatch . groups [ "ref" ] ;
250- const isLocalComponent = projectPath === `${ gitData . remote . group } /${ gitData . remote . project } ` && reference === gitData . commit . SHA ;
251- const context = {
252- isLocal : isLocalComponent ,
253- projectPath : projectPath ,
254- remote : gitData . remote ,
255- commit : gitData . commit ,
256- domain : domain ,
257- port : port ,
258- component : component ,
259- cwd : opts . cwd ,
260- } ;
261- const name = gitRemoteMatch . groups [ "componentName" ] ;
262- const version = getComponentVersion ( context , reference ) ;
263- const effectiveRef = version ?? reference ;
264- const sha = getComponentSha ( context , effectiveRef ) ;
259+ const { domain, projectPath, port, componentName, ref} = gitRemoteMatch . groups ;
260+ const isLocalComponent = projectPath === `${ gitData . remote . group } /${ gitData . remote . project } ` && ref === gitData . commit . SHA ;
265261 return {
266- domain : domain ,
267- port : port ,
268- projectPath : projectPath ,
269- componentPath : `templates/${ name } ` ,
270- effectiveRef : effectiveRef ,
271- name : name ,
272- reference : reference ,
273- sha : sha ,
274- version : version ,
262+ _cache : {
263+ version : undefined ,
264+ effectiveRef : undefined ,
265+ sha : undefined ,
266+ } ,
267+ gitData,
268+ domain,
269+ port,
270+ projectPath,
271+ componentPath : `templates/${ componentName } ` ,
272+ name : componentName ,
273+ reference : ref ,
274+ get version ( ) {
275+ if ( this . _cache . version === undefined ) {
276+ if ( this . isLocal ) {
277+ this . _cache . version = this . gitData . commit . SHA ;
278+ } else {
279+ const semanticVersionRangesPattern = / ^ \d + ( \. \d + ) ? $ / ;
280+ if ( this . reference == "~latest" || semanticVersionRangesPattern . test ( this . reference ) ) {
281+ // https://docs.gitlab.com/ci/components/#semantic-version-ranges
282+ const stdout = getGitRemoteInfo ( this , "--tags" ) ;
283+ const tags = stdout . split ( "\n" ) . map ( line => line . split ( "\t" ) [ 1 ] . split ( "/" ) [ 2 ] ) ;
284+ const version = resolveSemanticVersionRange ( this . reference , tags ) ;
285+ assert ( version , `This GitLab CI configuration is invalid: component: \`${ this . name } \` - The reference (${ this . reference } ) is invalid` ) ;
286+ this . _cache . version = version ;
287+ } else {
288+ this . _cache . version = null ;
289+ }
290+ }
291+ }
292+ return this . _cache . version ;
293+ } ,
294+ get effectiveRef ( ) {
295+ if ( this . _cache . effectiveRef === undefined ) {
296+ this . _cache . effectiveRef = this . version ?? this . reference ;
297+ }
298+ return this . _cache . effectiveRef ;
299+ } ,
300+ get sha ( ) {
301+ if ( this . _cache . sha === undefined ) {
302+ if ( this . isLocal ) {
303+ this . _cache . sha = this . gitData . commit . SHA ;
304+ } else if ( / ^ [ 0 - 9 a - f ] { 40 } $ / . test ( this . effectiveRef ) ) {
305+ // effectiveRef may already be a sha, if so return it directly
306+ this . _cache . sha = this . effectiveRef ;
307+ } else {
308+ const stdout = getGitRemoteInfo ( this ) ;
309+ const lines = stdout . split ( "\n" ) ;
310+ // annotated tags: prefer the deref'd commit sha (refs/tags/x^{})
311+ const match = lines . find ( line => line . endsWith ( `refs/tags/${ this . effectiveRef } ^{}` ) ) ??
312+ lines . find ( line =>
313+ line . endsWith ( `refs/tags/${ this . effectiveRef } ` ) ||
314+ line . endsWith ( `refs/heads/${ this . effectiveRef } ` ) ,
315+ ) ;
316+ assert ( match , `Could not resolve commit SHA for ${ this . effectiveRef } in ${ this . projectPath } ` ) ;
317+ this . _cache . sha = match . split ( "\t" ) [ 0 ] ;
318+ }
319+ }
320+ return this . _cache . sha ;
321+ } ,
275322 isLocal : isLocalComponent ,
276323 } ;
277324 }
@@ -459,53 +506,12 @@ export async function resolveIncludeLocal (pattern: string, cwd: string) {
459506 return repoFiles . filter ( ( f : any ) => re2js . matches ( f ) ) ;
460507}
461508
462- export function getGitRemoteInfo ( ctx : any , ...args : string [ ] ) {
509+ export function getGitRemoteInfo ( ctx : GitRemoteInfoContext , ...args : string [ ] ) {
463510 const cmdArgs = [ "git" , "ls-remote" , ...args ] ;
464- if ( ctx . remote . schema == "git" || ctx . remote . schema == "ssh" ) {
511+ if ( ctx . gitData . remote . schema == "git" || ctx . gitData . remote . schema == "ssh" ) {
465512 cmdArgs . push ( `git@${ ctx . domain } :${ ctx . projectPath } ` ) ;
466513 } else {
467- cmdArgs . push ( `${ ctx . remote . schema } ://${ ctx . domain } :${ ctx . port ?? 443 } /${ ctx . projectPath } .git` ) ;
514+ cmdArgs . push ( `${ ctx . gitData . remote . schema } ://${ ctx . domain } :${ ctx . port ?? 443 } /${ ctx . projectPath } .git` ) ;
468515 }
469516 return Utils . syncSpawn ( cmdArgs ) . stdout ;
470517}
471-
472- export function getComponentVersion ( ctx : any , reference : string ) {
473- if ( ! ctx . isLocal ) {
474- const semanticVersionRangesPattern = / ^ \d + ( \. \d + ) ? $ / ;
475- if ( reference == "~latest" || semanticVersionRangesPattern . test ( reference ) ) {
476- // https://docs.gitlab.com/ci/components/#semantic-version-ranges
477- const stdout = getGitRemoteInfo ( ctx , "--tags" ) ;
478- const tags = stdout
479- . split ( "\n" )
480- . map ( ( line ) => {
481- return line
482- . split ( "\t" ) [ 1 ]
483- . split ( "/" ) [ 2 ] ;
484- } ) ;
485- const version = resolveSemanticVersionRange ( reference , tags ) ;
486- assert ( version , `This GitLab CI configuration is invalid: component: \`${ ctx . component } \` - The reference (${ reference } ) is invalid` ) ;
487- return version ;
488- }
489- } else {
490- return ctx . commit . SHA ;
491- }
492- }
493-
494- export function getComponentSha ( ctx : any , effectiveRef : string ) {
495- if ( ctx . isLocal ) {
496- return ctx . commit . SHA ;
497- }
498- // effectiveRef may already be a sha, if so return it directly
499- if ( / ^ [ 0 - 9 a - f ] { 40 } $ / . test ( effectiveRef ) ) {
500- return effectiveRef ;
501- }
502- const stdout = getGitRemoteInfo ( ctx ) ;
503- const lines = stdout . split ( "\n" ) ;
504- // annotated tags: prefer the deref'd commit sha (refs/tags/x^{})
505- const match = lines . find ( line => line . endsWith ( `refs/tags/${ effectiveRef } ^{}` ) )
506- ?? lines . find ( line =>
507- line . endsWith ( `refs/tags/${ effectiveRef } ` ) ||
508- line . endsWith ( `refs/heads/${ effectiveRef } ` ) ) ;
509- assert ( match , `Could not resolve commit SHA for ${ effectiveRef } in ${ ctx . projectPath } ` ) ;
510- return match . split ( "\t" ) [ 0 ] ;
511- }
0 commit comments