<# .SYNOPSIS Links the extension sources into VS Code's extensions folder for development. .DESCRIPTION Packaging is the wrong loop while you are actually changing code. VS Code loads any folder under %USERPROFILE%\.vscode\extensions that has a package.json, so linking the source there reduces the edit cycle to "npm run compile" plus a window reload. The link is a directory junction rather than a symlink because junctions need no elevation and no Developer Mode on Windows, and VS Code cannot tell the difference. Run -Unlink before install.ps1: a linked source and an installed .vsix are two copies of the same extension id, and VS Code will load one of them arbitrarily. .PARAMETER Only Restrict to these extension ids. .PARAMETER Unlink Remove the links. .PARAMETER Compile Run npm run compile in each extension first. .EXAMPLE .\scripts\dev-link.ps1 -Compile .\scripts\dev-link.ps1 -Unlink #> [CmdletBinding()] param( [string[]] $Only, [switch] $Unlink, [switch] $Compile ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'common.ps1') $config = Get-SetupConfig $extensionsDir = Join-Path (Get-HomeDir) '.vscode/extensions' if (-not (Test-Path $extensionsDir)) { New-Item -ItemType Directory -Path $extensionsDir -Force | Out-Null } $extensions = $config.extensions if ($Only) { $extensions = $extensions | Where-Object { $Only -contains $_.id } $missing = $Only | Where-Object { $config.extensions.id -notcontains $_ } if ($missing) { throw "not in config.json: $($missing -join ', ')" } } foreach ($extension in $extensions) { $source = Get-ExtensionPath $extension $manifestPath = Join-Path $source 'package.json' if (-not (Test-Path $manifestPath)) { Write-Warning "no package.json in $source, skipping $($extension.id)" continue } $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json $linkName = "$($manifest.publisher).$($manifest.name)-$($manifest.version)" $link = Join-Path $extensionsDir $linkName if ($Unlink) { if (Test-Path $link) { # Remove-Item on a junction deletes the link, not the target -- but only # without -Recurse, which would walk into the source and delete the files. [System.IO.Directory]::Delete($link) Write-Host "unlinked $linkName" -ForegroundColor Green } else { Write-Host "$linkName was not linked" -ForegroundColor DarkGray } continue } if ($Compile) { Push-Location $source try { Write-Host "compiling $($extension.id)..." -ForegroundColor DarkGray if (-not (Test-Path (Join-Path $source 'node_modules'))) { & npm install --silent --no-audit --no-fund if ($LASTEXITCODE -ne 0) { throw "npm install failed for $($extension.id)" } } & npm run compile --silent if ($LASTEXITCODE -ne 0) { throw "compile failed for $($extension.id)" } } finally { Pop-Location } } $main = $manifest.main -replace '^\./', '' if (-not (Test-Path (Join-Path $source $main))) { Write-Warning ("$($extension.id) has no built output at $main -- it will fail to " + "activate. Pass -Compile.") } if (Test-Path $link) { $item = Get-Item $link -Force $target = $item.Target if ($target -and @($target)[0] -eq $source) { Write-Host "$linkName already linked" -ForegroundColor DarkGray continue } throw ("$link already exists and does not point at $source. Remove it by hand " + "(it may be a real installed copy).") } # A junction needs no privilege on Windows; elsewhere a plain symlink does the same job. $linkType = if (Test-IsWindows) { 'Junction' } else { 'SymbolicLink' } New-Item -ItemType $linkType -Path $link -Target $source | Out-Null Write-Host "linked $linkName -> $source" -ForegroundColor Green } Write-Host '' Write-Host 'Reload the window (Developer: Reload Window) to pick this up.' -ForegroundColor Cyan