Skip to content

Commit

Permalink
Breaking: Handle overwrite option in symlink to be consistent with dest
Browse files Browse the repository at this point in the history
  • Loading branch information
erikkemperman authored and phated committed Nov 30, 2017
1 parent 743d216 commit 08d33fb
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 17 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 30 additions & 17 deletions lib/symlink/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -50,14 +50,42 @@ 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)) {
return callback(symlinkErr);
}
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(
Expand All @@ -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;
118 changes: 118 additions & 0 deletions test/symlink.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 08d33fb

Please sign in to comment.