Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use symbolic link if possible in Windows #56

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
[![npm version](https://img.shields.io/npm/v/symlink-dir.svg)](https://www.npmjs.com/package/symlink-dir)
<!--/@-->

* Always uses "junctions" on Windows. Even though support for "symbolic links" was added in Vista+, users by default lack permission to create them
* Uses "junctions" on Windows if "symbolic links" is disallowed. Even though support for "symbolic links" was added in Vista+, users by default lack permission to create them
* If you prefer symbolic links in Windows, turn on the developer mode
* Any file or directory, that has the destination name, is renamed before creating the link

## Installation
Expand Down
66 changes: 51 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@ import renameOverwrite = require('rename-overwrite')

const IS_WINDOWS = process.platform === 'win32' || /^(msys|cygwin)$/.test(<string>process.env.OSTYPE)

// Always use "junctions" on Windows. Even though support for "symbolic links" was added in Vista+, users by default
// Falls back to "junctions" on Windows if "symbolic links" is disallowed. Even though support for "symbolic links" was added in Vista+, users by default
// lack permission to create them
const symlinkType = IS_WINDOWS ? 'junction' : 'dir'
let symlinkType: 'dir' | 'junction' = 'dir'

const resolveSrc = IS_WINDOWS ? resolveSrcOnWin: resolveSrcOnNonWin
let symlinkPermissionCheckDone = !IS_WINDOWS

function resolveSrcOnWin (src: string, dest: string) {
let resolveSrc = resolveSrcOnTrueSymlink

function resolveSrcOnWinJunction (src: string, dest: string) {
return `${src}\\`
}

function resolveSrcOnNonWin (src: string, dest: string) {
function resolveSrcOnTrueSymlink (src: string, dest: string) {
return pathLib.relative(pathLib.dirname(dest), src)
}

function resolveExistingLinkTarget (linkTarget: string, linkPath: string) {
if (!IS_WINDOWS) return linkTarget
// Can be absolute (junction or symlink) or relative (symlink) in Windows, so we need to unify to absolute path
return betterPathResolve(pathLib.isAbsolute(linkTarget) ? linkTarget : pathLib.resolve(pathLib.dirname(linkPath), linkTarget))
}

function symlinkDir (target: string, path: string, opts?: { overwrite?: boolean }): Promise<{ reused: boolean, warn?: string }> {
path = betterPathResolve(path)
target = betterPathResolve(target)

if (target === path) throw new Error(`Symlink path is the same as the target path (${target})`)

target = resolveSrc(target, path)

return forceSymlink(target, path, opts)
}

Expand All @@ -44,15 +50,31 @@ async function forceSymlink (
): Promise<{ reused: boolean, warn?: string }> {
let initialErr: Error
try {
await fs.symlink(target, path, symlinkType)
if (symlinkPermissionCheckDone) {
await fs.symlink(resolveSrc(target, path), path, symlinkType)
} else {
try {
await fs.symlink(resolveSrc(target, path), path, symlinkType)
symlinkPermissionCheckDone = true
} catch (err) {
if ((<NodeJS.ErrnoException>err).code === 'EPERM') {
await fs.symlink(resolveSrcOnWinJunction(target, path), path, 'junction')
symlinkType = 'junction'
resolveSrc = resolveSrcOnWinJunction
symlinkPermissionCheckDone = true
} else {
throw err
}
}
}
return { reused: false }
} catch (err) {
switch ((<NodeJS.ErrnoException>err).code) {
case 'ENOENT':
try {
await fs.mkdir(pathLib.dirname(path), { recursive: true })
} catch (mkdirError) {
mkdirError.message = `Error while trying to symlink "${target}" to "${path}". ` +
mkdirError.message = `Error while trying to symlink "${resolveSrc(target, path)}" to "${path}". ` +
`The error happened while trying to create the parent directory for the symlink target. ` +
`Details: ${mkdirError}`
throw mkdirError
Expand Down Expand Up @@ -97,7 +119,7 @@ async function forceSymlink (
}
}

if (target === linkString) {
if (target === resolveExistingLinkTarget(linkString, path)) {
return { reused: true }
}
if (opts?.overwrite === false) {
Expand All @@ -119,8 +141,6 @@ namespace symlinkDir {

if (target === path) throw new Error(`Symlink path is the same as the target path (${target})`)

target = resolveSrc(target, path)

return forceSymlinkSync(target, path, opts)
}
}
Expand All @@ -135,7 +155,23 @@ function forceSymlinkSync (
): { reused: boolean, warn?: string } {
let initialErr: Error
try {
symlinkSync(target, path, symlinkType)
if (symlinkPermissionCheckDone) {
symlinkSync(resolveSrc(target, path), path, symlinkType)
} else {
try {
symlinkSync(resolveSrc(target, path), path, symlinkType)
symlinkPermissionCheckDone = true
} catch (err) {
if ((<NodeJS.ErrnoException>err).code === 'EPERM') {
symlinkSync(resolveSrcOnWinJunction(target, path), path, 'junction')
symlinkType = 'junction'
resolveSrc = resolveSrcOnWinJunction
symlinkPermissionCheckDone = true
} else {
throw err
}
}
}
return { reused: false }
} catch (err) {
initialErr = err
Expand All @@ -144,7 +180,7 @@ function forceSymlinkSync (
try {
mkdirSync(pathLib.dirname(path), { recursive: true })
} catch (mkdirError) {
mkdirError.message = `Error while trying to symlink "${target}" to "${path}". ` +
mkdirError.message = `Error while trying to symlink "${resolveSrc(target, path)}" to "${path}". ` +
`The error happened while trying to create the parent directory for the symlink target. ` +
`Details: ${mkdirError}`
throw mkdirError
Expand Down Expand Up @@ -188,7 +224,7 @@ function forceSymlinkSync (
}
}

if (target === linkString) {
if (target === resolveExistingLinkTarget(linkString, path)) {
return { reused: true }
}
if (opts?.overwrite === false) {
Expand Down