151 lines
5.8 KiB
PowerShell
151 lines
5.8 KiB
PowerShell
[CmdletBinding()]
|
|
param()
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$bundleName = "zed-settings-sync.json"
|
|
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
|
|
|
function Require-Command([string]$Name, [string]$InstallHint) {
|
|
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
|
throw "$Name is required. Install it first: $InstallHint"
|
|
}
|
|
}
|
|
|
|
function Get-GistId([string]$Value) {
|
|
$matches = [regex]::Matches($Value.Trim(), "[a-fA-F0-9]{8,}")
|
|
if ($matches.Count -eq 0) {
|
|
throw "Enter a valid GitHub Gist ID or URL."
|
|
}
|
|
return $matches[$matches.Count - 1].Value.ToLowerInvariant()
|
|
}
|
|
|
|
function Test-AllowedPath([string]$Path) {
|
|
$fixed = @(
|
|
"settings.json",
|
|
"keymap.json",
|
|
"tasks.json",
|
|
"debug.json",
|
|
"scripts/toggle-file-scan-exclusions.mjs",
|
|
"scripts/file-exclusions.json",
|
|
"scripts/zed-settings-sync.mjs"
|
|
)
|
|
if ($Path.StartsWith("/") -or $Path.Contains("\") -or $Path.Split("/") -contains "..") {
|
|
return $false
|
|
}
|
|
return $fixed -contains $Path -or $Path.StartsWith("snippets/") -or $Path.StartsWith("themes/")
|
|
}
|
|
|
|
function Write-Atomic([string]$Path, [string]$Contents) {
|
|
$directory = Split-Path $Path -Parent
|
|
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
|
$temporary = Join-Path $directory ".$([IO.Path]::GetFileName($Path)).$PID.tmp"
|
|
try {
|
|
[IO.File]::WriteAllText($temporary, $Contents, $utf8)
|
|
Move-Item -LiteralPath $temporary -Destination $Path -Force
|
|
} finally {
|
|
Remove-Item -LiteralPath $temporary -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Require-Command "node" "winget install OpenJS.NodeJS.LTS"
|
|
if (-not $env:ZED_SETTINGS_BUNDLE_PATH) {
|
|
Require-Command "gh" "winget install GitHub.cli"
|
|
Remove-Item Env:GH_TOKEN,Env:GITHUB_TOKEN -ErrorAction SilentlyContinue
|
|
& gh auth status *> $null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host "GitHub authentication is required."
|
|
& gh auth login
|
|
if ($LASTEXITCODE -ne 0) { throw "GitHub authentication failed." }
|
|
}
|
|
}
|
|
|
|
$entered = if ($env:ZED_SETTINGS_GIST_ID) { $env:ZED_SETTINGS_GIST_ID } else { Read-Host "GitHub Gist ID or URL (leave blank to create a new Secret Gist)" }
|
|
if ($env:ZED_SETTINGS_BUNDLE_PATH) {
|
|
$gistId = Get-GistId $entered
|
|
$bundleSource = [IO.File]::ReadAllText($env:ZED_SETTINGS_BUNDLE_PATH, [Text.Encoding]::UTF8)
|
|
} else {
|
|
$token = (& gh auth token).Trim()
|
|
if ($LASTEXITCODE -ne 0 -or -not $token) { throw "Could not obtain the GitHub authentication token from gh." }
|
|
$headers = @{
|
|
Accept = "application/vnd.github+json"
|
|
Authorization = "Bearer $token"
|
|
"User-Agent" = "zed-settings-bootstrap"
|
|
"X-GitHub-Api-Version" = "2022-11-28"
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($entered)) {
|
|
$seedUrl = "https://git.okk.cool/purp1e/zed-sync/raw/branch/master/zed-settings-sync.json"
|
|
$bundleSource = (Invoke-WebRequest -UseBasicParsing -Uri $seedUrl).Content
|
|
$payload = @{
|
|
description = "Zed settings sync"
|
|
public = $false
|
|
files = @{ $bundleName = @{ content = $bundleSource } }
|
|
} | ConvertTo-Json -Depth 5
|
|
$created = Invoke-RestMethod -Method Post -Uri "https://api.github.com/gists" -Headers $headers -ContentType "application/json" -Body ([Text.Encoding]::UTF8.GetBytes($payload))
|
|
$gistId = $created.id
|
|
Write-Host "Created Secret Gist: $($created.html_url)"
|
|
} else {
|
|
$gistId = Get-GistId $entered
|
|
$gist = Invoke-RestMethod -Uri "https://api.github.com/gists/$gistId" -Headers $headers
|
|
$remoteFile = $gist.files.$bundleName
|
|
if (-not $remoteFile) { throw "Gist $gistId does not contain $bundleName." }
|
|
if ($remoteFile.truncated) { throw "$bundleName is too large for the Gist API response." }
|
|
$bundleSource = [string]$remoteFile.content
|
|
}
|
|
}
|
|
|
|
try {
|
|
$bundle = $bundleSource | ConvertFrom-Json
|
|
} catch {
|
|
throw "The Gist contains invalid JSON: $($_.Exception.Message)"
|
|
}
|
|
|
|
$properties = @($bundle.PSObject.Properties)
|
|
foreach ($required in @("settings.json", "keymap.json", "tasks.json")) {
|
|
if (-not ($properties.Name -contains $required)) { throw "The bundle is missing $required." }
|
|
}
|
|
foreach ($property in $properties) {
|
|
if (-not (Test-AllowedPath $property.Name) -or $property.Value -isnot [string]) {
|
|
throw "Invalid bundle file: $($property.Name)"
|
|
}
|
|
}
|
|
|
|
$target = Join-Path $env:APPDATA "Zed"
|
|
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
$backup = Join-Path $target "backups\gist-bootstrap-$timestamp"
|
|
New-Item -ItemType Directory -Force -Path $target, $backup, (Join-Path $target "scripts") | Out-Null
|
|
|
|
foreach ($property in $properties) {
|
|
$existing = Join-Path $target ($property.Name.Replace("/", "\"))
|
|
if (Test-Path -LiteralPath $existing) {
|
|
$backupPath = Join-Path $backup ($property.Name.Replace("/", "\"))
|
|
New-Item -ItemType Directory -Force -Path (Split-Path $backupPath -Parent) | Out-Null
|
|
Copy-Item -LiteralPath $existing -Destination $backupPath -Force
|
|
}
|
|
}
|
|
|
|
$toggleScript = (Join-Path $target "scripts\toggle-file-scan-exclusions.mjs").Replace("\", "/")
|
|
$syncScript = (Join-Path $target "scripts\zed-settings-sync.mjs").Replace("\", "/")
|
|
foreach ($property in $properties) {
|
|
$destination = Join-Path $target ($property.Name.Replace("/", "\"))
|
|
$contents = [string]$property.Value
|
|
if ($property.Name -eq "tasks.json") {
|
|
$contents = $contents.Replace("__TOGGLE_SCRIPT__", $toggleScript).Replace("__SYNC_SCRIPT__", $syncScript)
|
|
}
|
|
Write-Atomic $destination $contents
|
|
}
|
|
|
|
$bytes = $utf8.GetBytes($bundleSource)
|
|
$sha = [Security.Cryptography.SHA256]::Create()
|
|
try {
|
|
$hash = -join ($sha.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") })
|
|
} finally {
|
|
$sha.Dispose()
|
|
}
|
|
$syncState = [ordered]@{ gist_id = $gistId; last_synced_hash = $hash } | ConvertTo-Json
|
|
Write-Atomic (Join-Path $target "scripts\settings-sync.json") "$syncState`n"
|
|
|
|
Write-Host "Installed Zed settings from Gist $gistId"
|
|
Write-Host "SHA-256: $hash"
|
|
Write-Host "Backup: $backup"
|
|
Write-Host "Restart Zed to load the synchronized configuration."
|