Beta channel scaffold

This commit is contained in:
Joe Peavey
2026-07-23 23:43:18 -04:00
commit 6c34d89a2f
14 changed files with 647 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<!--
Helper project so `dotnet pack` can build a .nupkg straight from a .nuspec.
It produces no assemblies; all package contents come from the nuspec passed
in via -p:NuspecFile / -p:NuspecBasePath (see pack.ps1).
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<EnableDefaultItems>false</EnableDefaultItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<NoWarn>NU5128;NU5048;NU5125</NoWarn>
</PropertyGroup>
</Project>
+85
View File
@@ -0,0 +1,85 @@
<#
.SYNOPSIS
Packs Q-SYS plugin folders into .nupkg files.
.DESCRIPTION
For each plugin folder under plugins/ (or a single one via -Plugin):
1. Loads the plugin's .nuspec
2. Replaces the <description> element with the contents of
description.md, if that file exists next to the nuspec
3. Packs it into a .nupkg using `dotnet pack` (no Visual Studio needed)
Output lands in -OutputDir (default: dist/).
.EXAMPLE
./tools/pack.ps1 # pack every plugin
./tools/pack.ps1 -Plugin ExamplePlugin # pack one plugin
#>
[CmdletBinding()]
param(
[string]$Plugin,
[string]$OutputDir = "dist"
)
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
$pluginsDir = Join-Path $repoRoot "plugins"
$packProj = Join-Path $PSScriptRoot "pack.csproj"
$OutputDir = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $OutputDir))
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
# Which plugin folders to pack?
$targets = if ($Plugin) {
$dir = Join-Path $pluginsDir $Plugin
if (-not (Test-Path $dir)) { throw "Plugin folder not found: $dir" }
Get-Item $dir
} else {
Get-ChildItem $pluginsDir -Directory
}
$packed = @()
foreach ($dir in $targets) {
$nuspec = Get-ChildItem $dir.FullName -Filter *.nuspec | Select-Object -First 1
if (-not $nuspec) {
Write-Warning "Skipping $($dir.Name): no .nuspec found"
continue
}
# Load nuspec and inject description.md if present
[xml]$xml = Get-Content $nuspec.FullName -Raw
$descFile = Join-Path $dir.FullName "description.md"
if (Test-Path $descFile) {
$desc = (Get-Content $descFile -Raw).Trim()
# NuGet caps description at 4000 chars
if ($desc.Length -gt 4000) { $desc = $desc.Substring(0, 4000) }
$node = $xml.package.metadata.GetElementsByTagName("description")[0]
if (-not $node) {
$node = $xml.CreateElement("description", $xml.package.NamespaceURI)
$xml.package.metadata.AppendChild($node) | Out-Null
}
$node.InnerText = $desc # InnerText handles XML escaping
Write-Host " [$($dir.Name)] description injected from description.md"
}
$id = $xml.package.metadata.id
$version = $xml.package.metadata.version
# Write the effective nuspec to a temp file (source nuspec stays untouched)
$tmpNuspec = Join-Path ([System.IO.Path]::GetTempPath()) "$id.$version.nuspec"
$xml.Save($tmpNuspec)
Write-Host " [$($dir.Name)] packing $id $version ..."
dotnet pack $packProj `
-p:NuspecFile=$tmpNuspec `
-p:NuspecBasePath=$($dir.FullName) `
-o $OutputDir --nologo -v quiet
if ($LASTEXITCODE -ne 0) { throw "dotnet pack failed for $($dir.Name)" }
Remove-Item $tmpNuspec -ErrorAction SilentlyContinue
$pkg = Join-Path $OutputDir "$id.$version.nupkg"
$packed += $pkg
Write-Host " [$($dir.Name)] -> $pkg" -ForegroundColor Green
}
if ($packed.Count -eq 0) { throw "Nothing was packed." }
Write-Host "`nPacked $($packed.Count) package(s) into $OutputDir"
+90
View File
@@ -0,0 +1,90 @@
<#
.SYNOPSIS
List and delete package versions in the Gitea NuGet registry.
.DESCRIPTION
Listing without -Token uses the anonymous NuGet v3 search endpoint - the
same view Q-SYS Designer gets, so it doubles as a feed health check.
Listing with -Token uses Gitea's management API and adds publish dates.
Deleting always requires -Token (package write scope).
.EXAMPLE
./tools/packages.ps1 -List -Owner jodamaster
./tools/packages.ps1 -List -Owner jodamaster -Id ExamplePlugin -Token $env:GITEA_TOKEN
./tools/packages.ps1 -Delete -Owner jodamaster -Id ExamplePlugin -Version 1.0.0 -Token $env:GITEA_TOKEN
#>
[CmdletBinding(DefaultParameterSetName = "List")]
param(
[Parameter(ParameterSetName = "List")] [switch]$List,
[Parameter(ParameterSetName = "Delete", Mandatory)] [switch]$Delete,
[string]$GiteaUrl = "https://3f4dzl.gitea.cloud",
[Parameter(Mandatory)] [string]$Owner,
[Parameter(ParameterSetName = "List")]
[Parameter(ParameterSetName = "Delete", Mandatory)]
[string]$Id,
[Parameter(ParameterSetName = "Delete", Mandatory)] [string]$Version,
[string]$Token, # required for -Delete; optional for -List (adds publish dates)
[switch]$Force # skip the delete confirmation prompt
)
$ErrorActionPreference = "Stop"
$base = $GiteaUrl.TrimEnd('/')
$headers = @{}
if ($Token) { $headers["Authorization"] = "token $Token" }
# ---------------- Delete ----------------
if ($PSCmdlet.ParameterSetName -eq "Delete") {
if (-not $Token) { throw "Deleting requires -Token (package write scope)." }
if (-not $Force) {
$answer = Read-Host "Delete $Id $Version from $Owner's registry? This is immediate and permanent. [y/N]"
if ($answer -notmatch '^[Yy]') { Write-Host "Aborted."; exit 0 }
}
$uri = "$base/api/packages/$Owner/nuget/$Id/$Version"
Invoke-RestMethod -Uri $uri -Headers $headers -Method Delete | Out-Null
Write-Host "Deleted $Id $Version." -ForegroundColor Green
Write-Host "Note: Q-SYS expects strictly ascending versions - prefer publishing a higher"
Write-Host "version over deleting and re-using a version number (clients may have cached it)."
exit 0
}
# ---------------- List ----------------
if ($Token) {
# Management API: full info including publish dates (requires sign-in on Gitea Cloud)
$all = @(); $page = 1
while ($true) {
$uri = "$base/api/v1/packages/$($Owner)?type=nuget&limit=50&page=$page"
$batch = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get
if (-not $batch -or $batch.Count -eq 0) { break }
$all += $batch
if ($batch.Count -lt 50) { break }
$page++
}
if ($Id) { $all = $all | Where-Object { $_.name -ieq $Id } }
if (-not $all -or $all.Count -eq 0) { Write-Host "No packages found."; exit 0 }
$all | Sort-Object name, created_at | Group-Object name | ForEach-Object {
Write-Host "`n$($_.Name)" -ForegroundColor Cyan
$_.Group | ForEach-Object {
$created = ([datetime]$_.created_at).ToLocalTime().ToString("yyyy-MM-dd HH:mm")
Write-Host (" {0,-20} published {1}" -f $_.version, $created)
}
}
} else {
# Anonymous NuGet v3 search - exactly what Q-SYS Designer sees
$q = if ($Id) { $Id } else { "" }
$uri = "$base/api/packages/$Owner/nuget/query?q=$q&skip=0&take=100"
$result = Invoke-RestMethod -Uri $uri -Method Get
$items = $result.data
if ($Id) { $items = $items | Where-Object { $_.id -ieq $Id } }
if (-not $items -or $items.Count -eq 0) { Write-Host "No packages visible in the anonymous feed for '$Owner'."; exit 0 }
foreach ($pkg in $items) {
Write-Host "`n$($pkg.id) (latest: $($pkg.version))" -ForegroundColor Cyan
foreach ($v in $pkg.versions) { Write-Host " $($v.version)" }
Write-Host " Designer 'Add Personal Package' URL:" -ForegroundColor DarkGray
Write-Host " $base/api/packages/$Owner/nuget/registration/$($pkg.id)/index.json" -ForegroundColor Yellow
}
Write-Host "`n(Anonymous view = what Q-SYS Designer sees. Add -Token for publish dates.)"
}
Write-Host ""
+49
View File
@@ -0,0 +1,49 @@
<#
.SYNOPSIS
Packs and pushes Q-SYS plugin packages to a Gitea NuGet registry.
.DESCRIPTION
Runs pack.ps1, then pushes every .nupkg in dist/ to your Gitea instance.
Already-published versions are skipped (--skip-duplicate), so it is safe
to run repeatedly - only new version numbers actually upload.
.EXAMPLE
./tools/publish.ps1 -GiteaUrl https://gitea.example.com -Owner qsys-plugins -Token $env:GITEA_TOKEN
./tools/publish.ps1 -GiteaUrl https://gitea.example.com -Owner qsys-plugins -Token xxxx -Plugin ExamplePlugin
#>
[CmdletBinding()]
param(
[string]$GiteaUrl = "https://3f4dzl.gitea.cloud",
[Parameter(Mandatory)] [string]$Owner, # Gitea user or org that owns the packages
[Parameter(Mandatory)] [string]$Token, # Gitea personal access token (package write scope)
[string]$Plugin,
[string]$OutputDir = "dist"
)
$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
# 1. Pack
& (Join-Path $PSScriptRoot "pack.ps1") -Plugin $Plugin -OutputDir $OutputDir
# 2. Push - Gitea needs a NAMED source with basic-auth credentials;
# pushing straight to the URL with --api-key fails (go-gitea/gitea#20717).
$feed = "$($GiteaUrl.TrimEnd('/'))/api/packages/$Owner/nuget/index.json"
$dist = Join-Path $repoRoot $OutputDir
Write-Host "`nPushing to $feed"
dotnet nuget remove source gitea-publish 2>$null | Out-Null
dotnet nuget add source $feed --name gitea-publish `
--username publisher --password $Token --store-password-in-clear-text | Out-Null
try {
Get-ChildItem $dist -Filter *.nupkg | ForEach-Object {
Write-Host " pushing $($_.Name) ..."
dotnet nuget push $_.FullName --source gitea-publish --skip-duplicate
if ($LASTEXITCODE -ne 0) { throw "Push failed for $($_.Name)" }
}
} finally {
dotnet nuget remove source gitea-publish 2>$null | Out-Null
}
Write-Host "`nDone. Feed URL for Q-SYS Designer users: $feed" -ForegroundColor Green