Files
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

136 lines
4.3 KiB
PowerShell

<#
.SYNOPSIS
Installs the extensions into VS Code, from a Gitea release or from dist/.
.DESCRIPTION
By default this downloads the .vsix assets from the -Tag release and installs them,
so a fresh machine needs nothing but VS Code and this repo -- no Node, no toolchain.
Pass -Local to install what build.ps1 produced instead.
Every install passes --force. Without it VS Code declines to reinstall a version it
already has, which would silently do nothing every time you republish the same
version number.
.PARAMETER Tag
Release tag to install from. Defaults to "latest".
.PARAMETER Local
Install from dist/ rather than downloading.
.PARAMETER Only
Restrict to these extension ids.
.PARAMETER Uninstall
Remove the extensions instead of installing them.
.EXAMPLE
.\scripts\install.ps1
.\scripts\install.ps1 -Local
.\scripts\install.ps1 -Tag v0.1.0
.\scripts\install.ps1 -Uninstall
#>
[CmdletBinding()]
param(
[string] $Tag = 'latest',
[string] $Token,
[string[]] $Only,
[switch] $Local,
[switch] $Uninstall
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'common.ps1')
$config = Get-SetupConfig
$code = Get-CodeCommand
function Select-Configured {
param([string[]] $Ids)
$chosen = $config.extensions
if ($Ids) {
$chosen = $chosen | Where-Object { $Ids -contains $_.id }
$missing = $Ids | Where-Object { $config.extensions.id -notcontains $_ }
if ($missing) { throw "not in config.json: $($missing -join ', ')" }
}
$chosen
}
if ($Uninstall) {
foreach ($extension in Select-Configured $Only) {
# The manifest's publisher is "local", so the installed id is local.<name>.
$id = "local.$($extension.id)"
Write-Host "uninstalling $id" -ForegroundColor DarkGray
& $code --uninstall-extension $id
}
Write-Host 'Reload the window for this to take effect.' -ForegroundColor Cyan
return
}
$wanted = Select-Configured $Only
$packages = @()
if ($Local) {
$dist = Get-DistDir
foreach ($extension in $wanted) {
$found = @(Get-ChildItem -Path $dist -Filter "$($extension.id)-*.vsix" |
Sort-Object Name -Descending)
if ($found.Count -eq 0) {
throw "no package for $($extension.id) in $dist. Run build.ps1 first."
}
$packages += $found[0].FullName
}
} else {
$Token = Get-GiteaToken -Token $Token
$release = Get-GiteaRelease -Tag $Tag -Token $Token
if (-not $release) {
throw ("no release tagged '$Tag' in " +
"$($config.gitea.owner)/$($config.gitea.repo). Run publish.ps1 first.")
}
$assets = @()
if ($release.PSObject.Properties.Name -contains 'assets' -and $release.assets) {
$assets = @($release.assets)
}
if ($assets.Count -eq 0) { throw "release '$Tag' has no assets" }
# Downloads go to a temp folder, not dist/, so an install never disturbs a build.
$staging = Join-Path ([System.IO.Path]::GetTempPath()) "vsix-$Tag-$PID"
New-Item -ItemType Directory -Path $staging -Force | Out-Null
foreach ($extension in $wanted) {
$match = @($assets |
Where-Object { $_.name -like "$($extension.id)-*.vsix" } |
Sort-Object name -Descending)
if ($match.Count -eq 0) {
Write-Warning "release '$Tag' has no asset for $($extension.id), skipping"
continue
}
$asset = $match[0]
$target = Join-Path $staging $asset.name
Write-Host "downloading $($asset.name)" -ForegroundColor DarkGray
Save-GiteaAsset -Url $asset.browser_download_url -Destination $target -Token $Token
$packages += $target
}
}
if ($packages.Count -eq 0) { throw 'nothing to install' }
$failed = @()
foreach ($package in $packages) {
Write-Host "installing $(Split-Path -Leaf $package)" -NoNewline
& $code --install-extension $package --force
if ($LASTEXITCODE -eq 0) {
Write-Host ' ok' -ForegroundColor Green
} else {
Write-Host ' FAILED' -ForegroundColor Red
$failed += $package
}
}
Write-Host ''
if ($failed.Count -gt 0) {
throw "$($failed.Count) of $($packages.Count) failed to install: $($failed -join ', ')"
}
Write-Host "installed $($packages.Count) extension(s). Reload the window." -ForegroundColor Cyan