@@ -756,3 +756,149 @@ test('never joins a path that resolves back to the source (guards against data l
756756 // Ensure destination is not the same as source path
757757 t . not ( path . resolve ( out ) , path . resolve ( file ) ) ;
758758} ) ;
759+
760+ test ( 'deeply nested ../ in source and dest' , async t => {
761+ fs . mkdirSync ( t . context . tmp ) ;
762+ fs . mkdirSync ( path . join ( t . context . tmp , 'a/b/c/d/e/f' ) , { recursive : true } ) ;
763+ fs . writeFileSync ( path . join ( t . context . tmp , 'a/b/target.txt' ) , 'target' ) ;
764+
765+ await cpy ( [ '../../../../target.txt' ] , '../../../../output' , { cwd : path . join ( t . context . tmp , 'a/b/c/d/e/f' ) } ) ;
766+
767+ t . is ( read ( t . context . tmp , 'a/b/target.txt' ) , read ( t . context . tmp , 'a/b/output/target.txt' ) ) ;
768+ } ) ;
769+
770+ test ( 'mixed ../ and ./ in paths' , async t => {
771+ fs . mkdirSync ( t . context . tmp ) ;
772+ fs . mkdirSync ( path . join ( t . context . tmp , 'src/sub' ) , { recursive : true } ) ;
773+ fs . writeFileSync ( path . join ( t . context . tmp , 'file.txt' ) , 'content' ) ;
774+
775+ await cpy ( [ '../../file.txt' ] , '.././../output' , { cwd : path . join ( t . context . tmp , 'src/sub' ) } ) ;
776+
777+ t . is ( read ( t . context . tmp , 'file.txt' ) , read ( t . context . tmp , 'output/file.txt' ) ) ;
778+ } ) ;
779+
780+ test ( 'redundant path segments' , async t => {
781+ fs . mkdirSync ( t . context . tmp ) ;
782+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested' ) , { recursive : true } ) ;
783+ fs . writeFileSync ( path . join ( t . context . tmp , 'nested/file.txt' ) , 'content' ) ;
784+
785+ const results = await cpy ( [ './nested/../nested/file.txt' ] , './output' , { cwd : t . context . tmp } ) ;
786+
787+ t . is ( results . length , 1 ) ;
788+ t . true ( fs . existsSync ( results [ 0 ] ) ) ;
789+ t . is ( fs . readFileSync ( results [ 0 ] , 'utf8' ) , 'content' ) ;
790+ } ) ;
791+
792+ test ( 'empty path segments' , async t => {
793+ fs . mkdirSync ( t . context . tmp ) ;
794+ fs . writeFileSync ( path . join ( t . context . tmp , 'file.txt' ) , 'content' ) ;
795+
796+ await cpy ( [ './file.txt' ] , 'output//subdir' , { cwd : t . context . tmp } ) ;
797+
798+ t . is ( read ( t . context . tmp , 'file.txt' ) , read ( t . context . tmp , 'output/subdir/file.txt' ) ) ;
799+ } ) ;
800+
801+ test ( 'current directory references' , async t => {
802+ fs . mkdirSync ( t . context . tmp ) ;
803+ fs . writeFileSync ( path . join ( t . context . tmp , 'file.txt' ) , 'content' ) ;
804+
805+ await cpy ( [ './././file.txt' ] , './././output' , { cwd : t . context . tmp } ) ;
806+
807+ t . is ( read ( t . context . tmp , 'file.txt' ) , read ( t . context . tmp , 'output/file.txt' ) ) ;
808+ } ) ;
809+
810+ test ( 'source outside cwd with absolute destination' , async t => {
811+ fs . mkdirSync ( t . context . tmp ) ;
812+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested/deep' ) , { recursive : true } ) ;
813+ fs . writeFileSync ( path . join ( t . context . tmp , 'file.txt' ) , 'content' ) ;
814+
815+ const absoluteDest = path . join ( t . context . tmp , 'absolute-output' ) ;
816+ await cpy ( [ '../../file.txt' ] , absoluteDest , { cwd : path . join ( t . context . tmp , 'nested/deep' ) } ) ;
817+
818+ t . is ( read ( t . context . tmp , 'file.txt' ) , read ( t . context . tmp , 'absolute-output/file.txt' ) ) ;
819+ } ) ;
820+
821+ test ( 'source and dest both with trailing slashes' , async t => {
822+ fs . mkdirSync ( t . context . tmp ) ;
823+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested' ) , { recursive : true } ) ;
824+ fs . writeFileSync ( path . join ( t . context . tmp , 'nested/file.txt' ) , 'content' ) ;
825+
826+ await cpy ( [ '../nested/file.txt' ] , '../output/' , { cwd : path . join ( t . context . tmp , 'nested' ) } ) ;
827+
828+ t . is ( read ( t . context . tmp , 'nested/file.txt' ) , read ( t . context . tmp , 'output/file.txt' ) ) ;
829+ } ) ;
830+
831+ test ( 'both source and dest with ../ paths' , async t => {
832+ fs . mkdirSync ( t . context . tmp ) ;
833+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested/deep' ) , { recursive : true } ) ;
834+ fs . writeFileSync ( path . join ( t . context . tmp , 'nested/file.txt' ) , 'content' ) ;
835+
836+ await cpy ( [ '../file.txt' ] , '../output' , { cwd : path . join ( t . context . tmp , 'nested/deep' ) } ) ;
837+
838+ t . is ( read ( t . context . tmp , 'nested/file.txt' ) , read ( t . context . tmp , 'nested/output/file.txt' ) ) ;
839+ t . true ( fs . existsSync ( path . join ( t . context . tmp , 'nested/output/file.txt' ) ) ) ;
840+ } ) ;
841+
842+ test ( 'multiple ../ in both source and dest' , async t => {
843+ fs . mkdirSync ( t . context . tmp ) ;
844+ fs . mkdirSync ( path . join ( t . context . tmp , 'a/b/c/d' ) , { recursive : true } ) ;
845+ fs . writeFileSync ( path . join ( t . context . tmp , 'a/b/target.txt' ) , 'target' ) ;
846+
847+ await cpy ( [ '../../target.txt' ] , '../../output' , { cwd : path . join ( t . context . tmp , 'a/b/c/d' ) } ) ;
848+
849+ t . is ( read ( t . context . tmp , 'a/b/target.txt' ) , read ( t . context . tmp , 'a/b/output/target.txt' ) ) ;
850+ t . true ( fs . existsSync ( path . join ( t . context . tmp , 'a/b/output/target.txt' ) ) ) ;
851+ } ) ;
852+
853+ test ( 'source with ./ prefix and ../ destination' , async t => {
854+ fs . mkdirSync ( t . context . tmp ) ;
855+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested' ) , { recursive : true } ) ;
856+ fs . writeFileSync ( path . join ( t . context . tmp , 'nested/file.txt' ) , 'content' ) ;
857+
858+ await cpy ( [ './file.txt' ] , '../output' , { cwd : path . join ( t . context . tmp , 'nested' ) } ) ;
859+
860+ t . is ( read ( t . context . tmp , 'nested/file.txt' ) , read ( t . context . tmp , 'output/file.txt' ) ) ;
861+ } ) ;
862+
863+ test ( 'absolute source with relative destination' , async t => {
864+ fs . mkdirSync ( t . context . tmp ) ;
865+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested' ) , { recursive : true } ) ;
866+ fs . writeFileSync ( path . join ( t . context . tmp , 'file.txt' ) , 'content' ) ;
867+
868+ await cpy ( [ path . join ( t . context . tmp , 'file.txt' ) ] , '../output' , { cwd : path . join ( t . context . tmp , 'nested' ) } ) ;
869+
870+ t . is ( read ( t . context . tmp , 'file.txt' ) , read ( t . context . tmp , 'output/file.txt' ) ) ;
871+ } ) ;
872+
873+ test ( 'glob with ../ and flat option' , async t => {
874+ fs . mkdirSync ( t . context . tmp ) ;
875+ fs . mkdirSync ( path . join ( t . context . tmp , 'nested/deep' ) , { recursive : true } ) ;
876+ fs . writeFileSync ( path . join ( t . context . tmp , 'file1.js' ) , 'js1' ) ;
877+ fs . writeFileSync ( path . join ( t . context . tmp , 'file2.js' ) , 'js2' ) ;
878+
879+ await cpy ( [ '../../*.js' ] , '../output' , { cwd : path . join ( t . context . tmp , 'nested/deep' ) , flat : true } ) ;
880+
881+ t . is ( read ( t . context . tmp , 'file1.js' ) , read ( t . context . tmp , 'nested/output/file1.js' ) ) ;
882+ t . is ( read ( t . context . tmp , 'file2.js' ) , read ( t . context . tmp , 'nested/output/file2.js' ) ) ;
883+ } ) ;
884+
885+ test ( 'relative source outside cwd to relative destination' , async t => {
886+ const testRoot = temporaryDirectory ( ) ;
887+
888+ fs . mkdirSync ( path . join ( testRoot , 'cwd' ) , { recursive : true } ) ;
889+ fs . mkdirSync ( path . join ( testRoot , 'src/a/b' ) , { recursive : true } ) ;
890+ fs . writeFileSync ( path . join ( testRoot , 'src/a/b/foo.txt' ) , 'test content' ) ;
891+
892+ try {
893+ await cpy ( [ '../src/a/b/foo.txt' ] , '../dest' , {
894+ cwd : path . join ( testRoot , 'cwd' ) ,
895+ } ) ;
896+
897+ t . true ( fs . existsSync ( path . join ( testRoot , 'dest/foo.txt' ) ) ) ;
898+ t . is ( read ( testRoot , 'dest/foo.txt' ) , 'test content' ) ;
899+
900+ t . true ( fs . existsSync ( path . join ( testRoot , 'src/a/b/foo.txt' ) ) ) ;
901+ } finally {
902+ rimrafSync ( testRoot ) ;
903+ }
904+ } ) ;
0 commit comments