diff --git a/src/ln.js b/src/ln.js index 363c319..878fda1 100644 --- a/src/ln.js +++ b/src/ln.js @@ -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; diff --git a/test/ln.js b/test/ln.js index 9f35aba..e7cd049 100644 --- a/test/ln.js +++ b/test/ln.js @@ -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);