Skip to content

Commit b217c87

Browse files
erikkempermanphated
authored andcommitted
Breaking: Handle overwrite option in symlink to be consistent with dest
1 parent eab7cbc commit b217c87

File tree

3 files changed

+156
-17
lines changed

3 files changed

+156
-17
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,14 @@ Type: `Number`
242242

243243
Default: The process mode.
244244

245+
##### `options.overwrite`
246+
247+
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`.
248+
249+
Type: `Boolean` or `Function`
250+
251+
Default: `true` (always overwrite existing files)
252+
245253
##### `options.relative`
246254

247255
Whether or not the symlink should be relative or absolute.

lib/symlink/index.js

+30-17
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function symlink(outFolder, opt) {
3636
// * Most products CANNOT detect a directory is a Junction:
3737
// This has the side effect of possibly having a whole directory
3838
// deleted when a product is deleting the Junction directory.
39-
// For example, IntelliJ product lines will delete the entire
39+
// For example, JetBrains product lines will delete the entire
4040
// contents of the TARGET directory because the product does not
4141
// realize it's a symlink as the JVM and Node return false for isSymlink.
4242
var useJunctions = koalas(boolean(opt.useJunctions, file), (isWindows && isDirectory));
@@ -50,14 +50,42 @@ function symlink(outFolder, opt) {
5050
srcPath = path.relative(file.base, srcPath);
5151
}
5252

53-
fs.symlink(srcPath, file.path, symType, onSymlink);
53+
// Because fs.symlink does not allow atomic overwrite option with flags, we
54+
// delete and recreate if the link already exists and overwrite is true.
55+
if (file.flag === 'w') {
56+
// TODO What happens when we call unlink with windows junctions?
57+
fs.unlink(file.path, onUnlink);
58+
} else {
59+
fs.symlink(srcPath, file.path, symType, onSymlink);
60+
}
61+
62+
function onUnlink(unlinkErr) {
63+
if (unlinkErr && unlinkErr.code !== 'ENOENT') {
64+
return callback(unlinkErr);;
65+
}
66+
fs.symlink(srcPath, file.path, symType, onSymlink);
67+
}
5468

5569
function onSymlink(symlinkErr) {
5670
if (isErrorFatal(symlinkErr)) {
5771
return callback(symlinkErr);
5872
}
5973
callback(null, file);
6074
}
75+
76+
function isErrorFatal(err) {
77+
if (!err) {
78+
return false;
79+
}
80+
81+
if (err.code === 'EEXIST' && file.flag === 'wx') {
82+
// Handle scenario for file overwrite failures.
83+
return false;
84+
}
85+
86+
// Otherwise, this is a fatal error
87+
return true;
88+
}
6189
}
6290

6391
var stream = pumpify.obj(
@@ -70,19 +98,4 @@ function symlink(outFolder, opt) {
7098
return lead(stream);
7199
}
72100

73-
function isErrorFatal(err) {
74-
if (!err) {
75-
return false;
76-
}
77-
78-
// TODO: should we check file.flag like .dest()?
79-
if (err.code === 'EEXIST') {
80-
// Handle scenario for file overwrite failures.
81-
return false;
82-
}
83-
84-
// Otherwise, this is a fatal error
85-
return true;
86-
}
87-
88101
module.exports = symlink;

test/symlink.js

+118
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,124 @@ describe('symlink stream', function() {
542542
], assert);
543543
});
544544

545+
it('does not overwrite links with overwrite option set to false', function(done) {
546+
var existingContents = 'Lorem Ipsum';
547+
548+
var file = new File({
549+
base: inputBase,
550+
path: inputPath,
551+
contents: null,
552+
});
553+
554+
function assert(files) {
555+
var outputContents = fs.readFileSync(outputPath, 'utf8');
556+
557+
expect(files.length).toEqual(1);
558+
expect(outputContents).toEqual(existingContents);
559+
}
560+
561+
// Write expected file which should not be overwritten
562+
fs.mkdirSync(outputBase);
563+
fs.writeFileSync(outputPath, existingContents);
564+
565+
pipe([
566+
from.obj([file]),
567+
vfs.symlink(outputBase, { overwrite: false }),
568+
concat(assert),
569+
], done);
570+
});
571+
572+
it('overwrites links with overwrite option set to true', function(done) {
573+
var existingContents = 'Lorem Ipsum';
574+
575+
var file = new File({
576+
base: inputBase,
577+
path: inputPath,
578+
contents: null,
579+
});
580+
581+
function assert(files) {
582+
var outputContents = fs.readFileSync(outputPath, 'utf8');
583+
584+
expect(files.length).toEqual(1);
585+
expect(outputContents).toEqual(contents);
586+
}
587+
588+
// This should be overwritten
589+
fs.mkdirSync(outputBase);
590+
fs.writeFileSync(outputPath, existingContents);
591+
592+
pipe([
593+
from.obj([file]),
594+
vfs.symlink(outputBase, { overwrite: true }),
595+
concat(assert),
596+
], done);
597+
});
598+
599+
it('does not overwrite links with overwrite option set to a function that returns false', function(done) {
600+
var existingContents = 'Lorem Ipsum';
601+
602+
var file = new File({
603+
base: inputBase,
604+
path: inputPath,
605+
contents: null,
606+
});
607+
608+
function overwrite(f) {
609+
expect(f).toEqual(file);
610+
return false;
611+
}
612+
613+
function assert(files) {
614+
var outputContents = fs.readFileSync(outputPath, 'utf8');
615+
616+
expect(files.length).toEqual(1);
617+
expect(outputContents).toEqual(existingContents);
618+
}
619+
620+
// Write expected file which should not be overwritten
621+
fs.mkdirSync(outputBase);
622+
fs.writeFileSync(outputPath, existingContents);
623+
624+
pipe([
625+
from.obj([file]),
626+
vfs.symlink(outputBase, { overwrite: overwrite }),
627+
concat(assert),
628+
], done);
629+
});
630+
631+
it('overwrites links with overwrite option set to a function that returns true', function(done) {
632+
var existingContents = 'Lorem Ipsum';
633+
634+
var file = new File({
635+
base: inputBase,
636+
path: inputPath,
637+
contents: null,
638+
});
639+
640+
function overwrite(f) {
641+
expect(f).toEqual(file);
642+
return true;
643+
}
644+
645+
function assert(files) {
646+
var outputContents = fs.readFileSync(outputPath, 'utf8');
647+
648+
expect(files.length).toEqual(1);
649+
expect(outputContents).toEqual(contents);
650+
}
651+
652+
// This should be overwritten
653+
fs.mkdirSync(outputBase);
654+
fs.writeFileSync(outputPath, existingContents);
655+
656+
pipe([
657+
from.obj([file]),
658+
vfs.symlink(outputBase, { overwrite: overwrite }),
659+
concat(assert),
660+
], done);
661+
});
662+
545663
it('emits an end event', function(done) {
546664
var symlinkStream = vfs.symlink(outputBase);
547665

0 commit comments

Comments
 (0)