diff --git a/src/ln.js b/src/ln.js index 2cf87cd8..f1f4a73f 100644 --- a/src/ln.js +++ b/src/ln.js @@ -35,7 +35,19 @@ function _ln(options, source, dest) { var isAbsolute = (path.resolve(source) === sourcePath); dest = path.resolve(process.cwd(), String(dest)); - if (fs.existsSync(dest)) { + if (fs.existsSync(dest) && common.statFollowLinks(dest).isDirectory()) { + dest = path.join(dest, path.basename(sourcePath)); + } + + var destinationExists; + try { + fs.lstatSync(dest); + destinationExists = true; + } catch (err) { + destinationExists = false; + } + + if (destinationExists) { if (!options.force) { common.error('Destination file exists', { continue: true }); } @@ -47,7 +59,16 @@ function _ln(options, source, dest) { var isWindows = process.platform === 'win32'; var linkType = isWindows ? 'file' : null; var resolvedSourcePath = isAbsolute ? sourcePath : path.resolve(process.cwd(), path.dirname(dest), source); - if (!fs.existsSync(resolvedSourcePath)) { + + var resolvedSourceExists; + try { + fs.lstatSync(resolvedSourcePath); + resolvedSourceExists = true; + } catch (err) { + resolvedSourceExists = false; + } + + if (!resolvedSourceExists) { common.error('Source file does not exist', { continue: true }); } else if (isWindows && common.statFollowLinks(resolvedSourcePath).isDirectory()) { linkType = 'junction'; diff --git a/test/ln.js b/test/ln.js index b1682058..2f00f5a9 100644 --- a/test/ln.js +++ b/test/ln.js @@ -16,6 +16,7 @@ test.beforeEach(t => { }); test.afterEach.always(t => { + process.chdir(CWD); shell.rm('-rf', t.context.tmp); }); @@ -48,6 +49,13 @@ test('destination already exists', t => { t.is(result.code, 1); }); +test('destination already exists inside directory', t => { + shell.cd(t.context.tmp); + const result = shell.ln('-s', 'file1', './'); + t.truthy(shell.error()); + t.is(result.code, 1); +}); + test('non-existent source', t => { const result = shell.ln(`${t.context.tmp}/noexist`, `${t.context.tmp}/linkfile1`); t.truthy(shell.error()); @@ -134,7 +142,28 @@ test('To current directory', t => { t.truthy(fs.existsSync('dest/testfile.txt')); t.truthy(fs.existsSync('dir1/insideDir.txt')); t.falsy(fs.existsSync('dest/insideDir.txt')); - shell.cd('..'); +}); + +test('Inside existing directory', t => { + shell.cd(t.context.tmp); + const result = shell.ln('-s', 'external/node_script.js', './'); + t.is(result.code, 0); + t.falsy(result.stderr); + t.falsy(shell.error()); + t.truthy(fs.existsSync('node_script.js')); + t.is( + fs.readFileSync('external/node_script.js').toString(), + fs.readFileSync('node_script.js').toString() + ); +}); + +test('Link to dead source links', t => { + shell.cd(t.context.tmp); + const result = shell.ln('-s', 'badlink', 'link-to-dead-link'); + t.is(result.code, 0); + t.falsy(result.stderr); + t.falsy(shell.error()); + fs.lstatSync('link-to-dead-link'); }); test('-f option', t => { @@ -161,6 +190,18 @@ test('-sf option', t => { }); }); +test('Override dead destination links with -sf', t => { + shell.cd(t.context.tmp); + const result = shell.ln('-sf', 'file1.txt', 'badlink'); + t.is(result.code, 0); + t.falsy(result.stderr); + t.falsy(shell.error()); + t.is( + fs.readFileSync('file1.txt').toString(), + fs.readFileSync('badlink').toString() + ); +}); + test('Abspath regression', t => { utils.skipOnWinForEPERM(shell.ln.bind(shell, '-sf', 'file1', path.resolve(`${t.context.tmp}/abspath`)), () => { t.truthy(fs.existsSync(`${t.context.tmp}/abspath`));