import * as tl from "azure-pipelines-task-lib/task" ; import path = require ( "path" ) ; import fs = require ( "fs" ) ; import im = require ( "azure-pipelines-task-lib/internal" ) ; import minimatch = require ( "minimatch" ) ; function _debugMatchOptions ( options ) { tl . debug ( `matchOptions.debug: '${ options . debug }'` ) ; tl . debug ( `matchOptions.nobrace: '${ options . nobrace }'` ) ; tl . debug ( `matchOptions.noglobstar: '${ options . noglobstar }'` ) ; tl . debug ( `matchOptions.dot: '${ options . dot }'` ) ; tl . debug ( `matchOptions.noext: '${ options . noext }'` ) ; tl . debug ( `matchOptions.nocase: '${ options . nocase }'` ) ; tl . debug ( `matchOptions.nonull: '${ options . nonull }'` ) ; tl . debug ( `matchOptions.matchBase: '${ options . matchBase }'` ) ; tl . debug ( `matchOptions.nocomment: '${ options . nocomment }'` ) ; tl . debug ( `matchOptions.nonegate: '${ options . nonegate }'` ) ; tl . debug ( `matchOptions.flipNegate: '${ options . flipNegate }'` ) ; } function _getDefaultMatchOptions ( ) { return < tl . MatchOptions > { debug : false , nobrace : true , noglobstar : false , dot : true , noext : false , nocase : process . platform == "win32" , nonull : false , matchBase : false , nocomment : false , nonegate : false , flipNegate : false , } ; } function _debugFindOptions ( options ) { tl . debug ( `findOptions.allowBrokenSymbolicLinks: '${ options . allowBrokenSymbolicLinks }'` ) ; tl . debug ( `findOptions.followSpecifiedSymbolicLink: '${ options . followSpecifiedSymbolicLink }'` ) ; tl . debug ( `findOptions.followSymbolicLinks: '${ options . followSymbolicLinks }'` ) ; } function _getDefaultFindOptions ( ) { return < tl . FindOptions > { allowBrokenSymbolicLinks : false , followSpecifiedSymbolicLink : true , followSymbolicLinks : true , } ; } param defaultRoot param patterns param findOptions param matchOptions export function findMatch ( defaultRoot , patterns , findOptions ? , matchOptions ? ) { defaultRoot = defaultRoot || tl . getVariable ( "system.defaultWorkingDirectory" ) || process . cwd ( ) ; tl . debug ( `defaultRoot: '${ defaultRoot }'` ) ; patterns = patterns || [ ] ; patterns = typeof patterns == "string" ? ( [ patterns ] as string [ ] ) : patterns ; findOptions = findOptions || _getDefaultFindOptions ( ) ; _debugFindOptions ( findOptions ) ; matchOptions = matchOptions || _getDefaultMatchOptions ( ) ; _debugMatchOptions ( matchOptions ) ; defaultRoot = im . _normalizeSeparators ( defaultRoot ) ; let results = { } ; let originalMatchOptions = matchOptions ; for ( let pattern of patterns || [ ] ) { tl . debug ( `pattern: '${ pattern }'` ) ; pattern = ( pattern || "" ) . trim ( ) ; if ( ! pattern ) { tl . debug ( "skipping empty pattern" ) ; continue ; } let matchOptions = im . _cloneMatchOptions ( originalMatchOptions ) ; if ( ! matchOptions . nocomment && im . _startsWith ( pattern , "#" ) ) { tl . debug ( "skipping comment" ) ; continue ; } matchOptions . nocomment = true ; let negateCount = 0 ; if ( ! matchOptions . nonegate ) { while ( pattern . charAt ( negateCount ) == "!" ) { negateCount ++ ; } pattern = pattern . substring ( negateCount ) ; if ( negateCount ) { tl . debug ( `trimmed leading '!'. pattern: '${ pattern }'` ) ; } } let isIncludePattern = negateCount == 0 || ( negateCount % 2 == 0 && ! matchOptions . flipNegate ) || ( negateCount % 2 == 1 && matchOptions . flipNegate ) ; matchOptions . nonegate = true ; matchOptions . flipNegate = false ; let expanded ; let preExpanded = pattern ; if ( matchOptions . nobrace ) { expanded = [ pattern ] ; } else { tl . debug ( "expanding braces" ) ; let convertedPattern = process . platform == "win32" ? pattern . replace ( /\\/g , "/" ) : pattern ; expanded = ( minimatch as any ) . braceExpand ( convertedPattern ) ; } matchOptions . nobrace = true ; for ( let pattern of expanded ) { if ( expanded . length != 1 || pattern != preExpanded ) { tl . debug ( `pattern: '${ pattern }'` ) ; } pattern = ( pattern || "" ) . trim ( ) ; if ( ! pattern ) { tl . debug ( "skipping empty pattern" ) ; continue ; } if ( isIncludePattern ) { let findInfo = im . _getFindInfoFromPattern ( defaultRoot , pattern , matchOptions ) ; let findPath = findInfo . findPath ; tl . debug ( `findPath: '${ findPath }'` ) ; if ( ! findPath ) { tl . debug ( "skipping empty path" ) ; continue ; } tl . debug ( `statOnly: '${ findInfo . statOnly }'` ) ; let findResults = [ ] ; if ( findInfo . statOnly ) { try { fs . statSync ( findPath ) ; findResults . push ( findPath ) ; } catch ( err ) { if ( err . code != "ENOENT" ) { throw err ; } tl . debug ( "ENOENT" ) ; } } else { findResults = find ( findPath , findOptions ) ; } tl . debug ( `found ${ findResults . length } paths` ) ; tl . debug ( "applying include pattern" ) ; if ( findInfo . adjustedPattern != pattern ) { tl . debug ( `adjustedPattern: '${ findInfo . adjustedPattern }'` ) ; pattern = findInfo . adjustedPattern ; } let matchResults = minimatch . match ( findResults , pattern , matchOptions ) ; tl . debug ( matchResults . length + " matches" ) ; for ( let matchResult of matchResults ) { let key = process . platform == "win32" ? matchResult . toUpperCase ( ) : matchResult ; results [ key ] = matchResult ; } } else { if ( matchOptions . matchBase && ! im . _isRooted ( pattern ) && ( process . platform == "win32" ? pattern . replace ( /\\/g , "/" ) : pattern ) . indexOf ( "/" ) < 0 ) { tl . debug ( "matchBase and basename only" ) ; } else { pattern = im . _ensurePatternRooted ( defaultRoot , pattern ) ; tl . debug ( `after ensurePatternRooted, pattern: '${ pattern }'` ) ; } tl . debug ( "applying exclude pattern" ) ; let matchResults = minimatch . match ( Object . keys ( results ) . map ( ( key ) => results [ key ] ) , pattern , matchOptions ) ; tl . debug ( matchResults . length + " matches" ) ; for ( let matchResult of matchResults ) { let key = process . platform == "win32" ? matchResult . toUpperCase ( ) : matchResult ; delete results [ key ] ; } } } } let finalResult = Object . keys ( results ) . map ( ( key ) => results [ key ] ) . sort ( ) ; tl . debug ( finalResult . length + " final results" ) ; return finalResult ; } param findPath param options returns export function find ( findPath , options ? ) { if ( ! findPath ) { tl . debug ( "no path specified" ) ; return [ ] ; } findPath = path . normalize ( findPath ) ; tl . debug ( `findPath: '${ findPath }'` ) ; options = options || _getDefaultFindOptions ( ) ; _debugFindOptions ( options ) ; try { fs . lstatSync ( findPath ) ; } catch ( err ) { if ( err . code == "ENOENT" ) { tl . debug ( "0 results" ) ; return [ ] ; } throw err ; } try { let result = [ ] ; let stack = [ new _FindItem ( findPath , 1 ) ] ; let traversalChain = [ ] ; while ( stack . length ) { let item = stack . pop ( ) ! ; result . push ( item . path ) ; let stats ; if ( options . followSymbolicLinks ) { try { stats = fs . statSync ( item . path ) ; } catch ( err ) { if ( err . code == "ENOENT" && options . allowBrokenSymbolicLinks ) { stats = fs . lstatSync ( item . path ) ; tl . debug ( `  ${ item . path } (broken symlink)` ) ; } else { throw err ; } } } else if ( options . followSpecifiedSymbolicLink && result . length == 1 ) { try { stats = fs . statSync ( item . path ) ; } catch ( err ) { if ( err . code == "ENOENT" && options . allowBrokenSymbolicLinks ) { stats = fs . lstatSync ( item . path ) ; tl . debug ( `  ${ item . path } (broken symlink)` ) ; } else { throw err ; } } } else { stats = fs . lstatSync ( item . path ) ; } if ( stats . isDirectory ( ) ) { tl . debug ( `  ${ item . path } (directory)` ) ; if ( options . followSymbolicLinks ) { let realPath = fs . realpathSync ( item . path ) ; while ( traversalChain . length >= item . level ) { traversalChain . pop ( ) ; } if ( traversalChain . some ( ( x ) => x == realPath ) ) { tl . debug ( "    cycle detected" ) ; continue ; } traversalChain . push ( realPath ) ; } let childLevel = item . level + 1 ; let childItems = fs . readdirSync ( item . path ) . map ( ( childName ) => new _FindItem ( path . join ( item . path , childName ) , childLevel ) ) ; for ( var i = childItems . length - 1 ; i >= 0 ; i -- ) { stack . push ( childItems [ i ] ) ; } } else { tl . debug ( `  ${ item . path } (file)` ) ; } } tl . debug ( `${ result . length } results` ) ; return result ; } catch ( err ) { throw new Error ( tl . loc ( "LIB_OperationFailed" , "find" , err . message ) ) ; } } class _FindItem { public path : string ; public level : number ; public constructor ( path , level ) { this . path = path ; this . level = level ; } }