Skip to content

Commit

Permalink
Update: Mirror symlink logic in dest (#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
phated committed Nov 27, 2017
1 parent 45b2be0 commit 70703d2
Show file tree
Hide file tree
Showing 8 changed files with 623 additions and 102 deletions.
16 changes: 16 additions & 0 deletions lib/dest/options.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
'use strict';

var os = require('os');

var isWindows = (os.platform() === 'win32');

var config = {
cwd: {
type: 'string',
Expand Down Expand Up @@ -29,6 +33,18 @@ var config = {
type: ['string', 'boolean'],
default: false,
},
// Symlink options
useJunctions: {
type: 'boolean',
default: function(file) {
var isDirectory = file.isDirectory();
return (isWindows && isDirectory);
},
},
relative: {
type: 'boolean',
default: false,
},
};

module.exports = config;
10 changes: 5 additions & 5 deletions lib/dest/write-contents/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ var fo = require('../../file-operations');
function writeContents(optResolver) {

function writeFile(file, enc, callback) {
// Write it as a symlink
if (file.symlink) {
return writeSymbolicLink(file, optResolver, onWritten);
}

// If directory then mkdirp it
if (file.isDirectory()) {
return writeDir(file, optResolver, onWritten);
Expand All @@ -22,11 +27,6 @@ function writeContents(optResolver) {
return writeStream(file, optResolver, onWritten);
}

// Write it as a symlink
if (file.symlink) {
return writeSymbolicLink(file, optResolver, onWritten);
}

// Write it like normal
if (file.isBuffer()) {
return writeBuffer(file, optResolver, onWritten);
Expand Down
44 changes: 35 additions & 9 deletions lib/dest/write-contents/write-symbolic-link.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
'use strict';

var fs = require('graceful-fs');
var path = require('path');

var fo = require('../../file-operations');

function writeSymbolicLink(file, optResolver, onWritten) {
// TODO handle symlinks properly
fs.symlink(file.symlink, file.path, function(symlinkErr) {
if (fo.isFatalOverwriteError(symlinkErr)) {
return onWritten(symlinkErr);
}

onWritten();
});
var srcPath = file.symlink;

var isDirectory = file.isDirectory();

// This option provides a way to create a Junction instead of a
// Directory symlink on Windows. This comes with the following caveats:
// * NTFS Junctions cannot be relative.
// * NTFS Junctions MUST be directories.
// * NTFS Junctions must be on the same file system.
// * 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, 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 = optResolver.resolve('useJunctions', file);

var symDirType = useJunctions ? 'junction' : 'dir';
var symType = isDirectory ? symDirType : 'file';
var isRelative = optResolver.resolve('relative', file);

// This is done inside prepareWrite to use the adjusted file.base property
if (isRelative && !useJunctions) {
srcPath = path.relative(file.base, srcPath);
}

var flag = optResolver.resolve('flag', file);

var opts = {
flag: flag,
type: symType,
};

fo.symlink(srcPath, file.path, opts, onWritten);
}

module.exports = writeSymbolicLink;
26 changes: 26 additions & 0 deletions lib/file-operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ function updateMetadata(fd, file, callback) {
}
}

function symlink(srcPath, destPath, opts, callback) {
// 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 (opts.flag === 'w') {
// TODO What happens when we call unlink with windows junctions?
fs.unlink(destPath, onUnlink);
} else {
fs.symlink(srcPath, destPath, opts.type, onSymlink);
}

function onUnlink(unlinkErr) {
if (isFatalUnlinkError(unlinkErr)) {
return callback(unlinkErr);
}
fs.symlink(srcPath, destPath, opts.type, onSymlink);
}

function onSymlink(symlinkErr) {
if (isFatalOverwriteError(symlinkErr, opts.flag)) {
return callback(symlinkErr);
}
callback();
}
}

/*
Custom writeFile implementation because we need access to the
file descriptor after the write is complete.
Expand Down Expand Up @@ -388,6 +413,7 @@ module.exports = {
getOwnerDiff: getOwnerDiff,
isOwner: isOwner,
updateMetadata: updateMetadata,
symlink: symlink,
writeFile: writeFile,
createWriteStream: createWriteStream,
};
62 changes: 2 additions & 60 deletions lib/symlink/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
'use strict';

var path = require('path');

var fs = require('graceful-fs');
var pumpify = require('pumpify');
var through = require('through2');
var lead = require('lead');
var mkdirpStream = require('fs-mkdirp-stream');
var createResolver = require('resolve-options');

var fo = require('../file-operations');

var config = require('./options');
var prepare = require('./prepare');
var linkFile = require('./link-file');

var folderConfig = {
outFolder: {
Expand All @@ -24,59 +19,6 @@ function symlink(outFolder, opt) {
var optResolver = createResolver(config, opt);
var folderResolver = createResolver(folderConfig, { outFolder: outFolder });

function linkFile(file, enc, callback) {
// Fetch the path as it was before prepare.dest()
var srcPath = file.history[file.history.length - 2];

var isDirectory = file.isDirectory();

// This option provides a way to create a Junction instead of a
// Directory symlink on Windows. This comes with the following caveats:
// * NTFS Junctions cannot be relative.
// * NTFS Junctions MUST be directories.
// * NTFS Junctions must be on the same file system.
// * 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, 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 = optResolver.resolve('useJunctions', file);

var symDirType = useJunctions ? 'junction' : 'dir';
var symType = isDirectory ? symDirType : 'file';
var isRelative = optResolver.resolve('relative', file);

// This is done inside prepareWrite to use the adjusted file.base property
if (isRelative && !useJunctions) {
srcPath = path.relative(file.base, srcPath);
}

// Because fs.symlink does not allow atomic overwrite option with flags, we
// delete and recreate if the link already exists and overwrite is true.
var flag = optResolver.resolve('flag', file);
if (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 (fo.isFatalUnlinkError(unlinkErr)) {
return callback(unlinkErr);
}
fs.symlink(srcPath, file.path, symType, onSymlink);
}

function onSymlink(symlinkErr) {
if (fo.isFatalOverwriteError(symlinkErr, flag)) {
return callback(symlinkErr);
}
callback(null, file);
}
}

function dirpath(file, callback) {
var dirMode = optResolver.resolve('dirMode', file);

Expand All @@ -86,7 +28,7 @@ function symlink(outFolder, opt) {
var stream = pumpify.obj(
prepare(folderResolver, optResolver),
mkdirpStream.obj(dirpath),
through.obj(linkFile)
linkFile(optResolver)
);

// Sink the stream to start flowing
Expand Down
60 changes: 60 additions & 0 deletions lib/symlink/link-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use strict';

var path = require('path');

var through = require('through2');

var fo = require('../file-operations');

function linkStream(optResolver) {

function linkFile(file, enc, callback) {
// Fetch the path as it was before prepare.dest()
var srcPath = file.history[file.history.length - 2];

var isDirectory = file.isDirectory();

// This option provides a way to create a Junction instead of a
// Directory symlink on Windows. This comes with the following caveats:
// * NTFS Junctions cannot be relative.
// * NTFS Junctions MUST be directories.
// * NTFS Junctions must be on the same file system.
// * 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, 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 = optResolver.resolve('useJunctions', file);

var symDirType = useJunctions ? 'junction' : 'dir';
var symType = isDirectory ? symDirType : 'file';
var isRelative = optResolver.resolve('relative', file);

// This is done inside prepareWrite to use the adjusted file.base property
if (isRelative && !useJunctions) {
srcPath = path.relative(file.base, srcPath);
}

var flag = optResolver.resolve('flag', file);

var opts = {
flag: flag,
type: symType,
};

fo.symlink(srcPath, file.path, opts, onSymlink);

function onSymlink(symlinkErr) {
if (symlinkErr) {
return callback(symlinkErr);
}

callback(null, file);
}
}

return through.obj(linkFile);
}

module.exports = linkStream;
Loading

0 comments on commit 70703d2

Please sign in to comment.