diff --git a/README.md b/README.md index f675bf4b..a3fda616 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,14 @@ Type: `Number` Default: The process mode. +##### `options.overwrite` + +Whether or not existing files with the same path should be overwritten. Can also be a function that takes in a file and returns `true` or `false`. + +Type: `Boolean` or `Function` + +Default: `true` (always overwrite existing files) + ##### `options.relative` Whether or not the symlink should be relative or absolute. diff --git a/lib/symlink/index.js b/lib/symlink/index.js index 05dc0c29..304edc90 100644 --- a/lib/symlink/index.js +++ b/lib/symlink/index.js @@ -36,7 +36,7 @@ function symlink(outFolder, opt) { // * Most products CANNOT detect a directory is a Junction: // This has the side effect of possibly having a whole directory // deleted when a product is deleting the Junction directory. - // For example, IntelliJ product lines will delete the entire + // For example, JetBrains product lines will delete the entire // contents of the TARGET directory because the product does not // realize it's a symlink as the JVM and Node return false for isSymlink. var useJunctions = koalas(boolean(opt.useJunctions, file), (isWindows && isDirectory)); @@ -50,7 +50,21 @@ function symlink(outFolder, opt) { srcPath = path.relative(file.base, srcPath); } - fs.symlink(srcPath, file.path, symType, onSymlink); + // Because fs.symlink does not allow atomic overwrite option with flags, we + // delete and recreate if the link already exists and overwrite is true. + if (file.flag === 'w') { + // TODO What happens when we call unlink with windows junctions? + fs.unlink(file.path, onUnlink); + } else { + fs.symlink(srcPath, file.path, symType, onSymlink); + } + + function onUnlink(unlinkErr) { + if (unlinkErr && unlinkErr.code !== 'ENOENT') { + return callback(unlinkErr);; + } + fs.symlink(srcPath, file.path, symType, onSymlink); + } function onSymlink(symlinkErr) { if (isErrorFatal(symlinkErr)) { @@ -58,6 +72,20 @@ function symlink(outFolder, opt) { } callback(null, file); } + + function isErrorFatal(err) { + if (!err) { + return false; + } + + if (err.code === 'EEXIST' && file.flag === 'wx') { + // Handle scenario for file overwrite failures. + return false; + } + + // Otherwise, this is a fatal error + return true; + } } var stream = pumpify.obj( @@ -70,19 +98,4 @@ function symlink(outFolder, opt) { return lead(stream); } -function isErrorFatal(err) { - if (!err) { - return false; - } - - // TODO: should we check file.flag like .dest()? - if (err.code === 'EEXIST') { - // Handle scenario for file overwrite failures. - return false; - } - - // Otherwise, this is a fatal error - return true; -} - module.exports = symlink; diff --git a/test/symlink.js b/test/symlink.js index 021fbf97..f298cd9f 100644 --- a/test/symlink.js +++ b/test/symlink.js @@ -542,6 +542,124 @@ describe('symlink stream', function() { ], assert); }); + it('does not overwrite links with overwrite option set to false', function(done) { + var existingContents = 'Lorem Ipsum'; + + var file = new File({ + base: inputBase, + path: inputPath, + contents: null, + }); + + function assert(files) { + var outputContents = fs.readFileSync(outputPath, 'utf8'); + + expect(files.length).toEqual(1); + expect(outputContents).toEqual(existingContents); + } + + // Write expected file which should not be overwritten + fs.mkdirSync(outputBase); + fs.writeFileSync(outputPath, existingContents); + + pipe([ + from.obj([file]), + vfs.symlink(outputBase, { overwrite: false }), + concat(assert), + ], done); + }); + + it('overwrites links with overwrite option set to true', function(done) { + var existingContents = 'Lorem Ipsum'; + + var file = new File({ + base: inputBase, + path: inputPath, + contents: null, + }); + + function assert(files) { + var outputContents = fs.readFileSync(outputPath, 'utf8'); + + expect(files.length).toEqual(1); + expect(outputContents).toEqual(contents); + } + + // This should be overwritten + fs.mkdirSync(outputBase); + fs.writeFileSync(outputPath, existingContents); + + pipe([ + from.obj([file]), + vfs.symlink(outputBase, { overwrite: true }), + concat(assert), + ], done); + }); + + it('does not overwrite links with overwrite option set to a function that returns false', function(done) { + var existingContents = 'Lorem Ipsum'; + + var file = new File({ + base: inputBase, + path: inputPath, + contents: null, + }); + + function overwrite(f) { + expect(f).toEqual(file); + return false; + } + + function assert(files) { + var outputContents = fs.readFileSync(outputPath, 'utf8'); + + expect(files.length).toEqual(1); + expect(outputContents).toEqual(existingContents); + } + + // Write expected file which should not be overwritten + fs.mkdirSync(outputBase); + fs.writeFileSync(outputPath, existingContents); + + pipe([ + from.obj([file]), + vfs.symlink(outputBase, { overwrite: overwrite }), + concat(assert), + ], done); + }); + + it('overwrites links with overwrite option set to a function that returns true', function(done) { + var existingContents = 'Lorem Ipsum'; + + var file = new File({ + base: inputBase, + path: inputPath, + contents: null, + }); + + function overwrite(f) { + expect(f).toEqual(file); + return true; + } + + function assert(files) { + var outputContents = fs.readFileSync(outputPath, 'utf8'); + + expect(files.length).toEqual(1); + expect(outputContents).toEqual(contents); + } + + // This should be overwritten + fs.mkdirSync(outputBase); + fs.writeFileSync(outputPath, existingContents); + + pipe([ + from.obj([file]), + vfs.symlink(outputBase, { overwrite: overwrite }), + concat(assert), + ], done); + }); + it('emits an end event', function(done) { var symlinkStream = vfs.symlink(outputBase);