param( [string]$ExternalRelativePath = "node_modules" ) $ErrorActionPreference = "Stop" function Get-RelativeDirectoryPath([string]$BaseDirectory, [string]$TargetDirectory) { $basePath = [System.IO.Path]::GetFullPath($BaseDirectory) $targetPath = [System.IO.Path]::GetFullPath($TargetDirectory) if ([System.IO.Path]::GetPathRoot($basePath) -ne [System.IO.Path]::GetPathRoot($targetPath)) { throw "Relative symbolic links require the source and target to be on the same drive" } $separator = [string][System.IO.Path]::DirectorySeparatorChar if (-not $basePath.EndsWith($separator)) { $basePath += $separator } $baseUri = [System.Uri]::new($basePath) $targetUri = [System.Uri]::new($targetPath) return [System.Uri]::UnescapeDataString($baseUri.MakeRelativeUri($targetUri).ToString()).Replace("/", $separator) } $projectDir = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..")) $frontendDir = Join-Path $projectDir "frontend" $externalDir = [System.IO.Path]::GetFullPath((Join-Path $projectDir $ExternalRelativePath)) $linkPath = Join-Path $frontendDir "node_modules" New-Item -ItemType Directory -Force -Path $externalDir | Out-Null $linkTarget = Get-RelativeDirectoryPath $frontendDir $externalDir $item = Get-Item -LiteralPath $linkPath -Force -ErrorAction SilentlyContinue if ($null -ne $item) { if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) { throw "A real directory already exists at $linkPath; move or remove it before creating the symbolic link" } Remove-Item -LiteralPath $linkPath -Force } Push-Location $frontendDir try { $command = 'mklink /D "node_modules" "{0}"' -f $linkTarget & $env:ComSpec /d /c $command if ($LASTEXITCODE -ne 0) { throw "mklink failed with exit code $LASTEXITCODE" } } finally { Pop-Location } Write-Host "node_modules -> $linkTarget" Write-Host "physical directory: $externalDir"