Merge pull request #322 from BYK/linking-windows

fix(Windows): fix symlinking on Windows
This commit is contained in:
Ari Porad 2016-01-31 08:31:34 -08:00
commit 514e7b0c75
2 changed files with 90 additions and 59 deletions

View File

@ -1,7 +1,6 @@
var fs = require('fs');
var path = require('path');
var common = require('./common');
var os = require('os');
//@
//@ ### ln([options,] source, dest)
@ -42,15 +41,29 @@ function _ln(options, source, dest) {
}
if (options.symlink) {
if ((isAbsolute && !fs.existsSync(sourcePath)) || !fs.existsSync(path.resolve(process.cwd(), path.dirname(dest), source))) {
var isWindows = common.platform === 'win';
var linkType = isWindows ? 'file' : null;
var resolvedSourcePath = isAbsolute ? sourcePath : path.resolve(process.cwd(), path.dirname(dest), source);
if (!fs.existsSync(resolvedSourcePath)) {
common.error('Source file does not exist', true);
} else if (isWindows && fs.statSync(resolvedSourcePath).isDirectory()) {
linkType = 'junction';
}
try {
fs.symlinkSync(linkType === 'junction' ? resolvedSourcePath: source, dest, linkType);
} catch (err) {
common.error(err.message);
}
fs.symlinkSync(source, dest, os.platform() === "win32" ? "junction" : null);
} else {
if (!fs.existsSync(source)) {
common.error('Source file does not exist', true);
}
fs.linkSync(source, dest);
try {
fs.linkSync(source, dest);
} catch (err) {
common.error(err.message);
}
}
}
module.exports = _ln;

View File

@ -1,5 +1,6 @@
var shell = require('..');
var common = require('../src/common');
var isWindows = common.platform === 'win';
var assert = require('assert'),
fs = require('fs'),
@ -7,6 +8,19 @@ var assert = require('assert'),
shell.config.silent = true;
// On Windows, symlinks for files need admin permissions. This helper
// skips certain tests if we are on Windows and got an EPERM error
function skipOnWinForEPERM (action, test) {
action();
var error = shell.error();
if (isWindows && error && /EPERM:/.test(error)) {
console.log("Got EPERM when testing symlinks on Windows. Assuming non-admin environment and skipping test.");
} else {
test();
}
}
shell.rm('-rf', 'tmp');
shell.mkdir('tmp');
@ -45,13 +59,6 @@ assert.ok(shell.error());
// Valids
//
// On Windows, symlinks for files need admin permissions.
// It is also broken now since current implementation simply uses `'junction'` type which is only
// valid for directories.
// TODO: Fix this for Windows and also add symlink tests for directories
if (common.platform === 'win')
shell.exit(123);
shell.ln('tmp/file1', 'tmp/linkfile1');
assert(fs.existsSync('tmp/linkfile1'));
assert.equal(
@ -64,17 +71,25 @@ assert.equal(
'new content 1'
);
shell.ln('-s', 'file2', 'tmp/linkfile2');
assert(fs.existsSync('tmp/linkfile2'));
assert.equal(
fs.readFileSync('tmp/file2').toString(),
fs.readFileSync('tmp/linkfile2').toString()
);
fs.writeFileSync('tmp/file2', 'new content 2');
assert.equal(
fs.readFileSync('tmp/linkfile2').toString(),
'new content 2'
);
skipOnWinForEPERM(shell.ln.bind(shell, '-s', 'file2', 'tmp/linkfile2'), function () {
assert(fs.existsSync('tmp/linkfile2'));
assert.equal(
fs.readFileSync('tmp/file2').toString(),
fs.readFileSync('tmp/linkfile2').toString()
);
fs.writeFileSync('tmp/file2', 'new content 2');
assert.equal(
fs.readFileSync('tmp/linkfile2').toString(),
'new content 2'
);
});
// Symbolic link directory test
shell.mkdir('tmp/ln');
shell.touch('tmp/ln/hello');
shell.ln('-s', 'ln', 'tmp/dir1');
assert(fs.existsSync('tmp/ln/hello'));
assert(fs.existsSync('tmp/dir1/hello'));
shell.ln('-f', 'tmp/file1.js', 'tmp/file2.js');
assert(fs.existsSync('tmp/file2.js'));
@ -88,46 +103,49 @@ assert.equal(
'new content js'
);
shell.ln('-sf', 'file1.txt', 'tmp/file2.txt');
assert(fs.existsSync('tmp/file2.txt'));
assert.equal(
fs.readFileSync('tmp/file1.txt').toString(),
fs.readFileSync('tmp/file2.txt').toString()
);
fs.writeFileSync('tmp/file1.txt', 'new content txt');
assert.equal(
fs.readFileSync('tmp/file2.txt').toString(),
'new content txt'
);
skipOnWinForEPERM(shell.ln.bind(shell, '-sf', 'file1.txt', 'tmp/file2.txt'), function () {
assert(fs.existsSync('tmp/file2.txt'));
assert.equal(
fs.readFileSync('tmp/file1.txt').toString(),
fs.readFileSync('tmp/file2.txt').toString()
);
fs.writeFileSync('tmp/file1.txt', 'new content txt');
assert.equal(
fs.readFileSync('tmp/file2.txt').toString(),
'new content txt'
);
});
// Abspath regression
shell.ln('-sf', 'file1', path.resolve('tmp/abspath'));
assert(fs.existsSync('tmp/abspath'));
assert.equal(
fs.readFileSync('tmp/file1').toString(),
fs.readFileSync('tmp/abspath').toString()
);
fs.writeFileSync('tmp/file1', 'new content 3');
assert.equal(
fs.readFileSync('tmp/abspath').toString(),
'new content 3'
);
skipOnWinForEPERM(shell.ln.bind(shell, '-sf', 'file1', path.resolve('tmp/abspath')), function () {
assert(fs.existsSync('tmp/abspath'));
assert.equal(
fs.readFileSync('tmp/file1').toString(),
fs.readFileSync('tmp/abspath').toString()
);
fs.writeFileSync('tmp/file1', 'new content 3');
assert.equal(
fs.readFileSync('tmp/abspath').toString(),
'new content 3'
);
});
// Relative regression
shell.ln('-sf', 'file1.txt', 'tmp/file2.txt');
shell.mkdir('-p', 'tmp/new');
// Move the symlink first, as the reverse confuses `mv`.
shell.mv('tmp/file2.txt', 'tmp/new/file2.txt');
shell.mv('tmp/file1.txt', 'tmp/new/file1.txt');
assert(fs.existsSync('tmp/new/file2.txt'));
assert.equal(
fs.readFileSync('tmp/new/file1.txt').toString(),
fs.readFileSync('tmp/new/file2.txt').toString()
);
fs.writeFileSync('tmp/new/file1.txt', 'new content txt');
assert.equal(
fs.readFileSync('tmp/new/file2.txt').toString(),
'new content txt'
);
skipOnWinForEPERM(shell.ln.bind(shell, '-sf', 'file1.txt', 'tmp/file2.txt'), function () {
shell.mkdir('-p', 'tmp/new');
// Move the symlink first, as the reverse confuses `mv`.
shell.mv('tmp/file2.txt', 'tmp/new/file2.txt');
shell.mv('tmp/file1.txt', 'tmp/new/file1.txt');
assert(fs.existsSync('tmp/new/file2.txt'));
assert.equal(
fs.readFileSync('tmp/new/file1.txt').toString(),
fs.readFileSync('tmp/new/file2.txt').toString()
);
fs.writeFileSync('tmp/new/file1.txt', 'new content txt');
assert.equal(
fs.readFileSync('tmp/new/file2.txt').toString(),
'new content txt'
);
});
shell.exit(123);