@@ -13,8 +13,8 @@ type OptionCases<T extends string> = `-${Uppercase<T> | Lowercase<T>}`;
1313
1414type OptionsPermutations < T extends string , U extends string = '' > =
1515 T extends `${infer First } ${infer Rest } `
16- ? OptionCases < `${U } ${First } `> | OptionCases < `${First } ${U } `> | OptionsPermutations < Rest , `${U } ${First } `> | OptionCases < First >
17- : OptionCases < U > | '' ;
16+ ? OptionCases < `${U } ${First } `> | OptionCases < `${First } ${U } `> | OptionsPermutations < Rest , `${U } ${First } `> | OptionCases < First >
17+ : OptionCases < U > | '' ;
1818
1919export enum TaskResult {
2020 Succeeded = 0 ,
@@ -893,7 +893,7 @@ export function popd(index: string = ''): string[] {
893893 const _path = path . resolve ( dirStack . shift ( ) ! ) ;
894894 cd ( _path ) ;
895895 }
896-
896+
897897 return getActualStack ( ) ;
898898}
899899
@@ -1065,7 +1065,7 @@ export function ls(optionsOrPaths?: unknown, ...paths: unknown[]): string[] {
10651065 paths . push ( ...pathsFromOptions ) ;
10661066 }
10671067 }
1068-
1068+
10691069 if ( paths . length === 0 ) {
10701070 paths . push ( path . resolve ( '.' ) ) ;
10711071 }
@@ -1101,7 +1101,7 @@ export function ls(optionsOrPaths?: unknown, ...paths: unknown[]): string[] {
11011101 continue ;
11021102 }
11031103 const baseDir = pathsCopy . find ( p => entry . startsWith ( path . resolve ( p as string ) ) ) as string || path . resolve ( '.' ) ;
1104-
1104+
11051105 if ( fs . lstatSync ( entry ) . isDirectory ( ) && isRecursive ) {
11061106 preparedPaths . push ( ...fs . readdirSync ( entry ) . map ( x => path . join ( entry , x ) ) ) ;
11071107 entries . push ( path . relative ( baseDir , entry ) ) ;
@@ -1179,13 +1179,17 @@ export function cp(sourceOrOptions: unknown, destinationOrSource: string, option
11791179 throw new Error ( loc ( 'LIB_PathNotFound' , 'cp' , destination ) ) ;
11801180 }
11811181
1182- const lstatSource = fs . lstatSync ( source ) ;
1182+ var lstatSource = fs . lstatSync ( source ) ;
11831183
11841184 if ( ! force && fs . existsSync ( destination ) ) {
11851185 return ;
11861186 }
11871187
11881188 try {
1189+ if ( lstatSource . isSymbolicLink ( ) ) {
1190+ source = fs . readlinkSync ( source ) ;
1191+ lstatSource = fs . lstatSync ( source ) ;
1192+ }
11891193 if ( lstatSource . isFile ( ) ) {
11901194 if ( fs . existsSync ( destination ) && fs . lstatSync ( destination ) . isDirectory ( ) ) {
11911195 destination = path . join ( destination , path . basename ( source ) ) ;
@@ -1197,14 +1201,49 @@ export function cp(sourceOrOptions: unknown, destinationOrSource: string, option
11971201 fs . copyFileSync ( source , destination , fs . constants . COPYFILE_EXCL ) ;
11981202 }
11991203 } else {
1200- fs . cpSync ( source , path . join ( destination , path . basename ( source ) ) , { recursive , force } ) ;
1204+ copyDirectoryWithResolvedSymlinks ( source , path . join ( destination , path . basename ( source ) ) , force ) ;
12011205 }
12021206 } catch ( error ) {
12031207 throw new Error ( loc ( 'LIB_OperationFailed' , 'cp' , error ) ) ;
12041208 }
1205- } , [ ] , { retryCount, continueOnError} ) ;
1209+ } , [ ] , { retryCount, continueOnError } ) ;
12061210}
12071211
1212+ const copyDirectoryWithResolvedSymlinks = ( src : string , dest : string , force : boolean ) => {
1213+ var srcPath : string ;
1214+ var destPath : string ;
1215+ var entry : fs . Dirent ;
1216+ const entries = fs . readdirSync ( src , { withFileTypes : true } ) ;
1217+
1218+ if ( ! fs . existsSync ( dest ) ) {
1219+ fs . mkdirSync ( dest , { recursive : true } ) ;
1220+ }
1221+
1222+ for ( entry of entries ) {
1223+ srcPath = path . join ( src , entry . name ) ;
1224+ destPath = path . join ( dest , entry . name ) ;
1225+
1226+ if ( entry . isSymbolicLink ( ) ) {
1227+ // Resolve the symbolic link and copy the target
1228+ const resolvedPath = fs . readlinkSync ( srcPath ) ;
1229+ const stat = fs . lstatSync ( resolvedPath ) ;
1230+
1231+ if ( stat . isFile ( ) ) {
1232+ // Use the actual target file's name instead of the symbolic link's name
1233+ const targetFileName = path . basename ( resolvedPath ) ;
1234+ const targetDestPath = path . join ( dest , targetFileName ) ;
1235+ fs . copyFileSync ( resolvedPath , targetDestPath ) ;
1236+ } else if ( stat . isDirectory ( ) ) {
1237+ copyDirectoryWithResolvedSymlinks ( resolvedPath , destPath , force ) ;
1238+ }
1239+ } else if ( entry . isFile ( ) ) {
1240+ fs . copyFileSync ( srcPath , destPath ) ;
1241+ } else if ( entry . isDirectory ( ) ) {
1242+ copyDirectoryWithResolvedSymlinks ( srcPath , destPath , force ) ;
1243+ }
1244+ }
1245+ } ;
1246+
12081247type MoveOptionsVariants = OptionsPermutations < 'fn' > ;
12091248
12101249/**
@@ -1705,95 +1744,81 @@ function _legacyFindFiles_getMatchingItems(
17051744 */
17061745export function rmRF ( inputPath : string ) : void {
17071746 debug ( 'rm -rf ' + inputPath ) ;
1708-
17091747 if ( getPlatform ( ) == Platform . Windows ) {
17101748 // Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
17111749 // program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
17121750 try {
1713- if ( fs . statSync ( inputPath ) . isDirectory ( ) ) {
1751+ const lstats = fs . lstatSync ( inputPath ) ;
1752+ if ( lstats . isDirectory ( ) && ! lstats . isSymbolicLink ( ) ) {
17141753 debug ( 'removing directory ' + inputPath ) ;
17151754 childProcess . execFileSync ( "cmd.exe" , [ "/c" , "rd" , "/s" , "/q" , inputPath ] ) ;
1716- }
1717- else {
1755+
1756+ } else if ( lstats . isSymbolicLink ( ) ) {
1757+ debug ( 'removing symbolic link ' + inputPath ) ;
1758+ const realPath = fs . readlinkSync ( inputPath ) ;
1759+ if ( fs . existsSync ( realPath ) ) {
1760+ const stats = fs . statSync ( realPath ) ;
1761+ if ( stats . isDirectory ( ) ) {
1762+ childProcess . execFileSync ( "cmd.exe" , [ "/c" , "rd" , "/s" , "/q" , realPath ] ) ;
1763+ fs . unlinkSync ( inputPath ) ;
1764+ } else {
1765+ fs . unlinkSync ( inputPath ) ;
1766+ }
1767+ } else {
1768+ debug ( `Symbolic link '${ inputPath } ' points to a non-existing target '${ realPath } '. Removing the symbolic link.` ) ;
1769+ fs . unlinkSync ( inputPath ) ;
1770+ }
1771+ } else {
17181772 debug ( 'removing file ' + inputPath ) ;
17191773 childProcess . execFileSync ( "cmd.exe" , [ "/c" , "del" , "/f" , "/a" , inputPath ] ) ;
17201774 }
17211775 } catch ( err ) {
1722- // if you try to delete a file that doesn't exist, desired result is achieved
1723- // other errors are valid
1724- if ( err . code != 'ENOENT' ) {
1725- throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , err . message ) ) ;
1726- }
1727- }
1728-
1729- // Shelling out fails to remove a symlink folder with missing source, this unlink catches that
1730- try {
1731- fs . unlinkSync ( inputPath ) ;
1732- } catch ( err ) {
1733- // if you try to delete a file that doesn't exist, desired result is achieved
1734- // other errors are valid
1776+ debug ( 'Error: ' + err . message ) ;
17351777 if ( err . code != 'ENOENT' ) {
17361778 throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , err . message ) ) ;
17371779 }
17381780 }
17391781 } else {
1740- // get the lstats in order to workaround a bug in [email protected] where symlinks 1741- // with missing targets are not handled correctly by "rm('-rf', path)"
17421782 let lstats : fs . Stats ;
17431783
17441784 try {
17451785 if ( inputPath . includes ( '*' ) ) {
17461786 const entries = findMatch ( path . dirname ( inputPath ) , [ path . basename ( inputPath ) ] ) ;
17471787
17481788 for ( const entry of entries ) {
1749- fs . rmSync ( entry , { recursive : true , force : true } ) ;
1789+ rmRF ( entry ) ;
17501790 }
1751-
1752- return ;
17531791 } else {
17541792 lstats = fs . lstatSync ( inputPath ) ;
1793+ if ( lstats . isDirectory ( ) && ! lstats . isSymbolicLink ( ) ) {
1794+ debug ( 'removing directory ' + inputPath ) ;
1795+ fs . rmSync ( inputPath , { recursive : true , force : true } ) ;
1796+ } else if ( lstats . isSymbolicLink ( ) ) {
1797+ debug ( 'removing symbolic link ' + inputPath ) ;
1798+ const realPath = fs . readlinkSync ( inputPath ) ;
1799+ if ( fs . existsSync ( realPath ) ) {
1800+ const stats = fs . statSync ( realPath ) ;
1801+ if ( stats . isDirectory ( ) ) {
1802+ fs . rmSync ( realPath , { recursive : true , force : true } ) ;
1803+ fs . unlinkSync ( inputPath ) ;
1804+ } else {
1805+ fs . unlinkSync ( inputPath ) ;
1806+ }
1807+ } else {
1808+ debug ( `Symbolic link '${ inputPath } ' points to a non-existing target '${ realPath } '. Removing the symbolic link.` ) ;
1809+ fs . unlinkSync ( inputPath ) ;
1810+ }
1811+ } else {
1812+ debug ( 'removing file ' + inputPath ) ;
1813+ fs . unlinkSync ( inputPath ) ;
1814+ }
17551815 }
17561816 } catch ( err ) {
1757- // if you try to delete a file that doesn't exist, desired result is achieved
1758- // other errors are valid
1759- if ( err . code == 'ENOENT' ) {
1760- return ;
1761- }
1762-
1763- throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , err . message ) ) ;
1764- }
1765-
1766- if ( lstats . isDirectory ( ) ) {
1767- debug ( 'removing directory' ) ;
1768- try {
1769- fs . rmSync ( inputPath , { recursive : true , force : true } ) ;
1770- } catch ( errMsg ) {
1771- throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , errMsg ) ) ;
1772- }
1773-
1774- return ;
1775- } else if ( lstats . isSymbolicLink ( ) ) {
1776- debug ( 'removing symbolic link' ) ;
1777- try {
1778- fs . unlinkSync ( inputPath ) ;
1779- } catch ( errMsg ) {
1780- throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , errMsg ) ) ;
1781- }
1782-
1783- return ;
1784- }
1785-
1786- debug ( 'removing file' ) ;
1787- try {
1788- const entries = findMatch ( path . dirname ( inputPath ) , [ path . basename ( inputPath ) ] ) ;
1789-
1790- for ( const entry of entries ) {
1791- fs . rmSync ( entry , { recursive : true , force : true } ) ;
1817+ debug ( 'Error: ' + err . message ) ;
1818+ if ( err . code != 'ENOENT' ) {
1819+ throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , err . message ) ) ;
17921820 }
17931821 }
1794- catch ( err ) {
1795- throw new Error ( loc ( 'LIB_OperationFailed' , 'rmRF' , err . message ) ) ;
1796- }
17971822 }
17981823}
17991824
@@ -2726,4 +2751,4 @@ if (!global['_vsts_task_lib_loaded']) {
27262751 im . _loadData ( ) ;
27272752 im . _exposeProxySettings ( ) ;
27282753 im . _exposeCertSettings ( ) ;
2729- }
2754+ }
0 commit comments