Files
vs-code-setup/scripts/dev-link.ps1
T
max d45639bf2f Build, publish to Gitea releases, and install the three extensions
Answers two things: how to install these without a marketplace, and how to keep
settings in sync when VS Code's Settings Sync cannot be pointed at Gitea.

- build.ps1 packages each extension into dist/
- publish.ps1 creates a release and uploads them, replacing same-named assets so a
  rolling "latest" tag stays clean
- install.ps1 downloads a release's assets and installs them, or -Local from dist/
- settings.ps1 copies User/ between the repo and the machine, and installs the
  marketplace extensions recorded in extensions.txt
- dev-link.ps1 junctions the sources into ~/.vscode/extensions for development
- ci-release.sh plus a Gitea Actions workflow do the build and publish on a tag push

Settings are copied, not symlinked: an atomic save replaces a symlink with a regular
file and the sync stops without looking broken.

vsce/ pins vsce and undici so packaging works on Node 18, which current vsce does not
support.

test/run.ps1 drives the real scripts against a stub of the Gitea release API. It
caught the multipart Content-Disposition being unquoted on .NET Framework, and
Invoke-WebRequest dropping the Authorization header across a redirect.
2026-09-07 19:35:20 +02:00

112 lines
3.9 KiB
PowerShell

<#
.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 $env:USERPROFILE '.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).")
}
New-Item -ItemType Junction -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