Skip to content

Commit 971accb

Browse files
committed
Update: Mirror symlink logic in dest (#246)
1 parent 9f0ec1d commit 971accb

File tree

8 files changed

+623
-102
lines changed

8 files changed

+623
-102
lines changed

lib/dest/options.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
'use strict';
22

3+
var os = require('os');
4+
5+
var isWindows = (os.platform() === 'win32');
6+
37
var config = {
48
cwd: {
59
type: 'string',
@@ -29,6 +33,18 @@ var config = {
2933
type: ['string', 'boolean'],
3034
default: false,
3135
},
36+
// Symlink options
37+
useJunctions: {
38+
type: 'boolean',
39+
default: function(file) {
40+
var isDirectory = file.isDirectory();
41+
return (isWindows && isDirectory);
42+
},
43+
},
44+
relative: {
45+
type: 'boolean',
46+
default: false,
47+
},
3248
};
3349

3450
module.exports = config;

lib/dest/write-contents/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ var fo = require('../../file-operations');
1212
function writeContents(optResolver) {
1313

1414
function writeFile(file, enc, callback) {
15+
// Write it as a symlink
16+
if (file.symlink) {
17+
return writeSymbolicLink(file, optResolver, onWritten);
18+
}
19+
1520
// If directory then mkdirp it
1621
if (file.isDirectory()) {
1722
return writeDir(file, optResolver, onWritten);
@@ -22,11 +27,6 @@ function writeContents(optResolver) {
2227
return writeStream(file, optResolver, onWritten);
2328
}
2429

25-
// Write it as a symlink
26-
if (file.symlink) {
27-
return writeSymbolicLink(file, optResolver, onWritten);
28-
}
29-
3030
// Write it like normal
3131
if (file.isBuffer()) {
3232
return writeBuffer(file, optResolver, onWritten);
Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
'use strict';
22

3-
var fs = require('graceful-fs');
3+
var path = require('path');
44

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

77
function writeSymbolicLink(file, optResolver, onWritten) {
8-
// TODO handle symlinks properly
9-
fs.symlink(file.symlink, file.path, function(symlinkErr) {
10-
if (fo.isFatalOverwriteError(symlinkErr)) {
11-
return onWritten(symlinkErr);
12-
}
13-
14-
onWritten();
15-
});
8+
var srcPath = file.symlink;
9+
10+
var isDirectory = file.isDirectory();
11+
12+
// This option provides a way to create a Junction instead of a
13+
// Directory symlink on Windows. This comes with the following caveats:
14+
// * NTFS Junctions cannot be relative.
15+
// * NTFS Junctions MUST be directories.
16+
// * NTFS Junctions must be on the same file system.
17+
// * Most products CANNOT detect a directory is a Junction:
18+
// This has the side effect of possibly having a whole directory
19+
// deleted when a product is deleting the Junction directory.
20+
// For example, JetBrains product lines will delete the entire
21+
// contents of the TARGET directory because the product does not
22+
// realize it's a symlink as the JVM and Node return false for isSymlink.
23+
var useJunctions = optResolver.resolve('useJunctions', file);
24+
25+
var symDirType = useJunctions ? 'junction' : 'dir';
26+
var symType = isDirectory ? symDirType : 'file';
27+
var isRelative = optResolver.resolve('relative', file);
28+
29+
// This is done inside prepareWrite to use the adjusted file.base property
30+
if (isRelative && !useJunctions) {
31+
srcPath = path.relative(file.base, srcPath);
32+
}
33+
34+
var flag = optResolver.resolve('flag', file);
35+
36+
var opts = {
37+
flag: flag,
38+
type: symType,
39+
};
40+
41+
fo.symlink(srcPath, file.path, opts, onWritten);
1642
}
1743

1844
module.exports = writeSymbolicLink;

lib/file-operations.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,31 @@ function updateMetadata(fd, file, callback) {
243243
}
244244
}
245245

246+
function symlink(srcPath, destPath, opts, callback) {
247+
// Because fs.symlink does not allow atomic overwrite option with flags, we
248+
// delete and recreate if the link already exists and overwrite is true.
249+
if (opts.flag === 'w') {
250+
// TODO What happens when we call unlink with windows junctions?
251+
fs.unlink(destPath, onUnlink);
252+
} else {
253+
fs.symlink(srcPath, destPath, opts.type, onSymlink);
254+
}
255+
256+
function onUnlink(unlinkErr) {
257+
if (isFatalUnlinkError(unlinkErr)) {
258+
return callback(unlinkErr);
259+
}
260+
fs.symlink(srcPath, destPath, opts.type, onSymlink);
261+
}
262+
263+
function onSymlink(symlinkErr) {
264+
if (isFatalOverwriteError(symlinkErr, opts.flag)) {
265+
return callback(symlinkErr);
266+
}
267+
callback();
268+
}
269+
}
270+
246271
/*
247272
Custom writeFile implementation because we need access to the
248273
file descriptor after the write is complete.
@@ -388,6 +413,7 @@ module.exports = {
388413
getOwnerDiff: getOwnerDiff,
389414
isOwner: isOwner,
390415
updateMetadata: updateMetadata,
416+
symlink: symlink,
391417
writeFile: writeFile,
392418
createWriteStream: createWriteStream,
393419
};

lib/symlink/index.js

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
'use strict';
22

3-
var path = require('path');
4-
5-
var fs = require('graceful-fs');
63
var pumpify = require('pumpify');
7-
var through = require('through2');
84
var lead = require('lead');
95
var mkdirpStream = require('fs-mkdirp-stream');
106
var createResolver = require('resolve-options');
117

12-
var fo = require('../file-operations');
13-
148
var config = require('./options');
159
var prepare = require('./prepare');
10+
var linkFile = require('./link-file');
1611

1712
var folderConfig = {
1813
outFolder: {
@@ -24,59 +19,6 @@ function symlink(outFolder, opt) {
2419
var optResolver = createResolver(config, opt);
2520
var folderResolver = createResolver(folderConfig, { outFolder: outFolder });
2621

27-
function linkFile(file, enc, callback) {
28-
// Fetch the path as it was before prepare.dest()
29-
var srcPath = file.history[file.history.length - 2];
30-
31-
var isDirectory = file.isDirectory();
32-
33-
// This option provides a way to create a Junction instead of a
34-
// Directory symlink on Windows. This comes with the following caveats:
35-
// * NTFS Junctions cannot be relative.
36-
// * NTFS Junctions MUST be directories.
37-
// * NTFS Junctions must be on the same file system.
38-
// * Most products CANNOT detect a directory is a Junction:
39-
// This has the side effect of possibly having a whole directory
40-
// deleted when a product is deleting the Junction directory.
41-
// For example, JetBrains product lines will delete the entire
42-
// contents of the TARGET directory because the product does not
43-
// realize it's a symlink as the JVM and Node return false for isSymlink.
44-
var useJunctions = optResolver.resolve('useJunctions', file);
45-
46-
var symDirType = useJunctions ? 'junction' : 'dir';
47-
var symType = isDirectory ? symDirType : 'file';
48-
var isRelative = optResolver.resolve('relative', file);
49-
50-
// This is done inside prepareWrite to use the adjusted file.base property
51-
if (isRelative && !useJunctions) {
52-
srcPath = path.relative(file.base, srcPath);
53-
}
54-
55-
// Because fs.symlink does not allow atomic overwrite option with flags, we
56-
// delete and recreate if the link already exists and overwrite is true.
57-
var flag = optResolver.resolve('flag', file);
58-
if (flag === 'w') {
59-
// TODO What happens when we call unlink with windows junctions?
60-
fs.unlink(file.path, onUnlink);
61-
} else {
62-
fs.symlink(srcPath, file.path, symType, onSymlink);
63-
}
64-
65-
function onUnlink(unlinkErr) {
66-
if (fo.isFatalUnlinkError(unlinkErr)) {
67-
return callback(unlinkErr);
68-
}
69-
fs.symlink(srcPath, file.path, symType, onSymlink);
70-
}
71-
72-
function onSymlink(symlinkErr) {
73-
if (fo.isFatalOverwriteError(symlinkErr, flag)) {
74-
return callback(symlinkErr);
75-
}
76-
callback(null, file);
77-
}
78-
}
79-
8022
function dirpath(file, callback) {
8123
var dirMode = optResolver.resolve('dirMode', file);
8224

@@ -86,7 +28,7 @@ function symlink(outFolder, opt) {
8628
var stream = pumpify.obj(
8729
prepare(folderResolver, optResolver),
8830
mkdirpStream.obj(dirpath),
89-
through.obj(linkFile)
31+
linkFile(optResolver)
9032
);
9133

9234
// Sink the stream to start flowing

lib/symlink/link-file.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict';
2+
3+
var path = require('path');
4+
5+
var through = require('through2');
6+
7+
var fo = require('../file-operations');
8+
9+
function linkStream(optResolver) {
10+
11+
function linkFile(file, enc, callback) {
12+
// Fetch the path as it was before prepare.dest()
13+
var srcPath = file.history[file.history.length - 2];
14+
15+
var isDirectory = file.isDirectory();
16+
17+
// This option provides a way to create a Junction instead of a
18+
// Directory symlink on Windows. This comes with the following caveats:
19+
// * NTFS Junctions cannot be relative.
20+
// * NTFS Junctions MUST be directories.
21+
// * NTFS Junctions must be on the same file system.
22+
// * Most products CANNOT detect a directory is a Junction:
23+
// This has the side effect of possibly having a whole directory
24+
// deleted when a product is deleting the Junction directory.
25+
// For example, JetBrains product lines will delete the entire
26+
// contents of the TARGET directory because the product does not
27+
// realize it's a symlink as the JVM and Node return false for isSymlink.
28+
var useJunctions = optResolver.resolve('useJunctions', file);
29+
30+
var symDirType = useJunctions ? 'junction' : 'dir';
31+
var symType = isDirectory ? symDirType : 'file';
32+
var isRelative = optResolver.resolve('relative', file);
33+
34+
// This is done inside prepareWrite to use the adjusted file.base property
35+
if (isRelative && !useJunctions) {
36+
srcPath = path.relative(file.base, srcPath);
37+
}
38+
39+
var flag = optResolver.resolve('flag', file);
40+
41+
var opts = {
42+
flag: flag,
43+
type: symType,
44+
};
45+
46+
fo.symlink(srcPath, file.path, opts, onSymlink);
47+
48+
function onSymlink(symlinkErr) {
49+
if (symlinkErr) {
50+
return callback(symlinkErr);
51+
}
52+
53+
callback(null, file);
54+
}
55+
}
56+
57+
return through.obj(linkFile);
58+
}
59+
60+
module.exports = linkStream;

0 commit comments

Comments
 (0)