-
-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix #210 Windows symbolic links requiring admin
- Loading branch information
Showing
6 changed files
with
206 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Create a hard-link on the File System | ||
// This function is better suited for operating | ||
// systems such as Windows due to the restrictions | ||
// placed on creating a symlink directory as a User | ||
// with Administrative rights. | ||
'use strict'; | ||
|
||
var path = require('path'); | ||
var fs = require('graceful-fs'); | ||
var through2 = require('through2'); | ||
var valueOrFunction = require('value-or-function'); | ||
var koalas = require('koalas'); | ||
|
||
var sink = require('../sink'); | ||
var prepareWrite = require('../prepare-write'); | ||
|
||
var boolean = valueOrFunction.boolean; | ||
|
||
// When creating a hard-link the type is determined | ||
// automatically, thus the type is not required when | ||
// invoking fs#link(). A hard link only works when the | ||
// paths are on the same file system. However, a hard link | ||
// points to the i-node instead of the filename and supports | ||
// easier dependency management when (link)ing assets such as a | ||
// node_module into a sub directory. | ||
function hardlink(dest, opt) { | ||
if (!opt) { | ||
opt = {}; | ||
} | ||
|
||
function linkFile(file, enc, callback) { | ||
var srcPath = file.path; | ||
|
||
var isRelative = koalas(boolean(opt.relative, file), false); | ||
|
||
prepareWrite(dest, file, opt, onPrepare); | ||
|
||
function onPrepare(prepareErr) { | ||
if (prepareErr) { | ||
return callback(prepareErr); | ||
} | ||
|
||
// This is done inside prepareWrite to use the adjusted file.base property | ||
if (isRelative) { | ||
srcPath = path.relative(file.base, srcPath); | ||
} | ||
|
||
// As a hardlink points to the file system i-node | ||
// the idea of relative or absolute does not exist. | ||
// However, to ensure the source and destination are | ||
// linked correctly, the above code is still used. | ||
fs.link(srcPath, file.path, onLink); | ||
} | ||
|
||
function onLink(linkErr) { | ||
if (isErrorFatal(linkErr)) { | ||
return callback(linkErr); | ||
} | ||
callback(null, file); | ||
} | ||
} | ||
|
||
var stream = through2.obj(opt, linkFile); | ||
|
||
// Sink the stream to start flowing | ||
// Do this on nextTick, it will flow at slowest speed of piped streams | ||
process.nextTick(sink(stream)); | ||
return 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 = hardlink; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
'use strict'; | ||
|
||
var fs = require('graceful-fs'); | ||
var expect = require('expect'); | ||
var miss = require('mississippi'); | ||
|
||
var vfs = require('../'); | ||
|
||
var cleanup = require('./utils/cleanup'); | ||
var testConstants = require('./utils/test-constants'); | ||
var isWin = require('./utils/is-windows'); | ||
|
||
var pipe = miss.pipe; | ||
var concat = miss.concat; | ||
|
||
var outputBase = testConstants.outputBase; | ||
var inputDirpath = testConstants.inputDirpath; | ||
var outputDirpath = testConstants.outputDirpath; | ||
var linkDirpath = testConstants.linkDirpath; | ||
var linkNestedTarget = testConstants.linkNestedTarget; | ||
var linkNestedFirst = testConstants.linkNestedFirst; | ||
var linkNestedSecond = testConstants.linkNestedSecond; | ||
|
||
var clean = cleanup([outputBase]); | ||
|
||
describe('.src() with hard symlinks', function() { | ||
|
||
beforeEach(clean); | ||
afterEach(clean); | ||
|
||
beforeEach(function(done) { | ||
fs.mkdirSync(outputBase); | ||
fs.mkdirSync(outputDirpath); | ||
// Directories cannot be hardlinked on any OS, use a softlink instead | ||
fs.symlinkSync(inputDirpath, linkDirpath, isWin ? 'junction' : 'dir'); | ||
// Hardlink the test file to our 'test' directory file | ||
fs.linkSync(linkNestedTarget, linkNestedSecond); | ||
fs.linkSync(linkNestedSecond, linkNestedFirst); | ||
done(); | ||
}); | ||
|
||
it('follows hardlinks correctly', function(done) { | ||
function assert(files) { | ||
expect(files.length).toEqual(1); | ||
// The path should be the link itself | ||
expect(files[0].path).toEqual(linkNestedFirst); | ||
// But the content should be what's in the actual file | ||
expect(files[0].contents.toString()).toEqual('symlink works\n'); | ||
// And the stats should not report it as a symlink | ||
expect(files[0].stat.isSymbolicLink()).toEqual(false); | ||
expect(files[0].stat.isFile()).toEqual(true); | ||
} | ||
|
||
pipe([ | ||
vfs.src(linkNestedFirst), | ||
concat(assert), | ||
], done); | ||
}); | ||
|
||
it('preserves original file contents', function(done) { | ||
function assert(files) { | ||
expect(files.length).toEqual(1); | ||
// Remove both linked files | ||
fs.unlink(linkNestedFirst); | ||
fs.unlink(linkNestedSecond); | ||
// Content should remain when referenced by 'original'. | ||
expect(files[0].contents.toString()).toEqual('symlink works\n'); | ||
} | ||
|
||
pipe([ | ||
vfs.src(linkNestedTarget), | ||
concat(assert), | ||
], done); | ||
}); | ||
|
||
it('preserves first hardlinked file contents', function(done) { | ||
function assert(files) { | ||
expect(files.length).toEqual(1); | ||
// Remove one hardlinked file | ||
fs.unlink(linkNestedSecond); | ||
// Check first hardlinked file | ||
expect(files[0].contents.toString()).toEqual('symlink works\n'); | ||
} | ||
|
||
pipe([ | ||
vfs.src(linkNestedFirst), | ||
concat(assert), | ||
], done); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters