@@ -10,6 +10,7 @@ import axios from "axios";
10
10
import globby from "globby" ;
11
11
import path from "path" ;
12
12
import semver from "semver" ;
13
+ import RE2 from "re2" ;
13
14
14
15
type ParserIncludesInitOptions = {
15
16
argv : Argv ;
@@ -66,13 +67,7 @@ export class ParserIncludes {
66
67
continue ;
67
68
}
68
69
}
69
- if ( value [ "local" ] ) {
70
- validateIncludeLocal ( value [ "local" ] ) ;
71
- const files = await globby ( value [ "local" ] . replace ( / ^ \/ / , "" ) , { dot : true , cwd} ) ;
72
- if ( files . length == 0 ) {
73
- throw new AssertionError ( { message : `Local include file cannot be found ${ value [ "local" ] } ` } ) ;
74
- }
75
- } else if ( value [ "file" ] ) {
70
+ if ( value [ "file" ] ) {
76
71
for ( const fileValue of Array . isArray ( value [ "file" ] ) ? value [ "file" ] : [ value [ "file" ] ] ) {
77
72
promises . push ( this . downloadIncludeProjectFile ( cwd , stateDir , value [ "project" ] , value [ "ref" ] || "HEAD" , fileValue , gitData , fetchIncludes ) ) ;
78
73
}
@@ -97,9 +92,13 @@ export class ParserIncludes {
97
92
}
98
93
}
99
94
if ( value [ "local" ] ) {
100
- const files = await globby ( [ value [ "local" ] . replace ( / ^ \/ / , "" ) ] , { dot : true , cwd} ) ;
95
+ validateIncludeLocal ( value [ "local" ] ) ;
96
+ const files = resolveIncludeLocal ( value [ "local" ] , cwd ) ;
97
+ if ( files . length == 0 ) {
98
+ throw new AssertionError ( { message : `Local include file cannot be found ${ value [ "local" ] } ` } ) ;
99
+ }
101
100
for ( const localFile of files ) {
102
- const content = await Parser . loadYaml ( ` ${ cwd } / ${ localFile } ` , { inputs : value . inputs || { } } , expandVariables ) ;
101
+ const content = await Parser . loadYaml ( localFile , { inputs : value . inputs ?? { } } , expandVariables ) ;
103
102
includeDatas = includeDatas . concat ( await this . init ( content , opts ) ) ;
104
103
}
105
104
} else if ( value [ "project" ] ) {
@@ -321,6 +320,18 @@ export class ParserIncludes {
321
320
throw new AssertionError ( { message : `Project include could not be fetched { project: ${ project } , ref: ${ ref } , file: ${ normalizedFile } }\n${ e } ` } ) ;
322
321
}
323
322
}
323
+
324
+ static readonly memoLocalRepoFiles = ( ( ) => {
325
+ const cache = new Map < string , string [ ] > ( ) ;
326
+ return ( path : string ) => {
327
+ let result = cache . get ( path ) ;
328
+ if ( typeof result !== "undefined" ) return result ;
329
+
330
+ result = globby . sync ( path , { dot : true , gitignore : true } ) ;
331
+ cache . set ( path , result ) ;
332
+ return result ;
333
+ } ;
334
+ } ) ( ) ;
324
335
}
325
336
326
337
export function validateIncludeLocal ( filePath : string ) {
@@ -345,3 +356,24 @@ export function resolveSemanticVersionRange (range: string, gitTags: string[]) {
345
356
} ) ;
346
357
return found ;
347
358
}
359
+
360
+ export function resolveIncludeLocal ( pattern : string , cwd : string ) {
361
+ const repoFiles = ParserIncludes . memoLocalRepoFiles ( cwd ) ;
362
+
363
+ if ( ! pattern . startsWith ( "/" ) ) pattern = `/${ pattern } ` ; // Ensure pattern starts with `/`
364
+ pattern = `${ cwd } ${ pattern } ` ;
365
+
366
+ // escape all special regex metacharacters
367
+ pattern = pattern . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ;
368
+
369
+ // `**` matches anything
370
+ const anything = ".*?" ;
371
+ pattern = pattern . replace ( / \\ \* \\ \* / g, anything ) ;
372
+
373
+ // `*` matches anything except for `/`
374
+ const anything_but_not_slash = "([^/])*?" ;
375
+ pattern = pattern . replace ( / \\ \* / g, anything_but_not_slash ) ;
376
+
377
+ const re2 = new RE2 ( `^${ pattern } ` ) ;
378
+ return repoFiles . filter ( ( f : any ) => re2 . test ( f ) ) ;
379
+ }
0 commit comments