Beta channel scaffold
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
name: Publish Q-SYS plugins
|
||||
|
||||
# Packs every plugin under plugins/ and pushes any NEW versions to this
|
||||
# Gitea instance's NuGet registry. Versions that already exist in the feed
|
||||
# are skipped, so publishing a revision is just:
|
||||
# bump <version> in the plugin's .nuspec -> commit -> push to main.
|
||||
#
|
||||
# Required repository secret:
|
||||
# PACKAGE_TOKEN - a Gitea personal access token with package read/write
|
||||
# scope for the owner account/org.
|
||||
|
||||
# Beta channel: MANUAL publish only, same as production. Run it from
|
||||
# Actions -> "Publish Q-SYS plugins" -> Run workflow when a beta build is
|
||||
# ready. (To auto-publish on every commit to a beta build instead, add the
|
||||
# push: block back - beta is low-stakes, but manual keeps it deliberate.)
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Install PowerShell
|
||||
run: dotnet tool install --global PowerShell --version 7.4.6
|
||||
|
||||
- name: Pack all plugins
|
||||
run: ~/.dotnet/tools/pwsh ./tools/pack.ps1 -OutputDir dist
|
||||
|
||||
- name: Verify PACKAGE_TOKEN secret
|
||||
run: |
|
||||
if [ -z "${{ secrets.PACKAGE_TOKEN }}" ]; then
|
||||
echo "::error::PACKAGE_TOKEN is EMPTY - the secret is missing, misnamed, or not visible to this repo's workflows"
|
||||
exit 1
|
||||
fi
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: token ${{ secrets.PACKAGE_TOKEN }}" \
|
||||
"${{ github.server_url }}/api/v1/packages/${{ github.repository_owner }}?type=nuget&limit=1")
|
||||
echo "Token validity check (package API): HTTP $code (200 = valid)"
|
||||
if [ "$code" != "200" ]; then
|
||||
echo "::error::PACKAGE_TOKEN is present but Gitea rejects it - regenerate the token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Push new versions to Gitea NuGet registry
|
||||
# Primary: dotnet client with a named basic-auth source (Gitea's
|
||||
# documented method). Fallback: direct multipart PUT with diagnostics.
|
||||
run: |
|
||||
feed="${{ github.server_url }}/api/packages/${{ github.repository_owner }}/nuget"
|
||||
dotnet nuget add source "$feed/index.json" --name gitea \
|
||||
--username "${{ github.actor }}" \
|
||||
--password "${{ secrets.PACKAGE_TOKEN }}" \
|
||||
--store-password-in-clear-text
|
||||
if dotnet nuget push "dist/*.nupkg" --source gitea --skip-duplicate; then
|
||||
echo "Published via dotnet client."
|
||||
exit 0
|
||||
fi
|
||||
echo "dotnet push failed - trying direct multipart upload..."
|
||||
fail=0
|
||||
for f in dist/*.nupkg; do
|
||||
code=$(curl -s -o /tmp/push-resp -w "%{http_code}" \
|
||||
-u "${{ github.actor }}:${{ secrets.PACKAGE_TOKEN }}" \
|
||||
-X PUT -F "package=@$f;type=application/octet-stream" "$feed/")
|
||||
case "$code" in
|
||||
201) echo "PUBLISHED: $f" ;;
|
||||
409) echo "skipped (version already in feed): $f" ;;
|
||||
*) echo "::error::HTTP $code pushing $f"; cat /tmp/push-resp; echo; fail=1 ;;
|
||||
esac
|
||||
done
|
||||
exit $fail
|
||||
@@ -0,0 +1,4 @@
|
||||
dist/
|
||||
tools/bin/
|
||||
tools/obj/
|
||||
*.nupkg
|
||||
@@ -0,0 +1,52 @@
|
||||
# Visionary PacketAV — BETA channel registry
|
||||
|
||||
A parallel copy of the production plugin registry that publishes a **separate
|
||||
package identity** for pre-release testing. Testers point Q-SYS at a different
|
||||
URL and get beta builds; everyone else stays on the production feed untouched.
|
||||
|
||||
## What makes this the beta channel
|
||||
|
||||
| | Production | Beta (this repo) |
|
||||
|---|---|---|
|
||||
| Package id | `Visionary-PacketAV-Suite` | `Visionary-PacketAV-Suite-Beta` |
|
||||
| Library entry | Visionary PacketAV Suite | Visionary PacketAV Suite (Beta) |
|
||||
| Add-package URL | `https://vsplug.in/packetav` | `https://vsplug.in/packetav-beta` |
|
||||
| In-plugin update feed | `https://vsplug.in/versions` | `https://vsplug.in/versions-beta` |
|
||||
| Notifications channel | `VSI_PacketAV_Versions` | `VSI_PacketAV_Versions_Beta` |
|
||||
| Version lineage | its own 3-part counter | its own 3-part counter |
|
||||
|
||||
Both packages live in the **same Gitea org** (`visionary-plugins`), because
|
||||
Gitea owns NuGet packages at the org level. The distinct package **id** is what
|
||||
gives Q-SYS a separate My Packages entry — cloning the repo alone would not.
|
||||
|
||||
## Important: never run both channels on one machine
|
||||
|
||||
The beta plugins keep the **same `PluginInfo.Id`** as production so testers
|
||||
exercise the real blocks. That means Designer will see two files with the same
|
||||
id if both the prod and beta packages are installed, and one will shadow the
|
||||
other unpredictably. A tester is on **either** prod **or** beta, never both.
|
||||
See `docs/BETA-tester-instructions.md`.
|
||||
|
||||
## Publishing a beta build
|
||||
|
||||
1. Drop the beta `.qplugx` (and `icon.png` + any PDFs) into
|
||||
`plugins/VisionarySolutions_Suite_Beta/content/`.
|
||||
2. Bump `<version>` in `VisionarySolutions_Suite_Beta.nuspec` (3-part SemVer,
|
||||
strictly higher than the last beta — **never** a 4-part number).
|
||||
3. Commit and push to `main`.
|
||||
4. Actions → **Publish Q-SYS plugins** → **Run workflow** (manual by design).
|
||||
5. Update the beta update feed: replace `versions-beta.json` in the assets repo
|
||||
so in-plugin update checks on beta builds see the new version.
|
||||
|
||||
## Promoting a beta to production
|
||||
|
||||
When a beta build passes, the **same `.qplugx` files** move to the production
|
||||
repo's `content/`, bump the **production** nuspec version, and publish there.
|
||||
Nothing is rebuilt — the plugin files (and their PluginInfo.Ids) are identical;
|
||||
only the package wrapper differs. Then update the production `versions.json`.
|
||||
|
||||
## Everything else
|
||||
|
||||
CI, `tools/pack.ps1`, the SemVer rules, the anonymous-read requirement, and the
|
||||
same `PACKAGE_TOKEN` secret all work exactly as in the production repo — see
|
||||
that repo's README and `docs/publishing-lessons.md`.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Joining the Visionary PacketAV Beta
|
||||
|
||||
Thank you for helping test pre-release builds. The beta plugins are the real
|
||||
blocks that will ship — you're seeing them a cycle early. Setup is the same as
|
||||
the normal plugins, just a different URL.
|
||||
|
||||
## Before you start — one rule that matters
|
||||
|
||||
**Do not have both the regular and the beta Visionary PacketAV packages
|
||||
installed on the same computer.** The beta plugins share the same internal IDs
|
||||
as the production plugins (that's what makes them a true test), so if Q-SYS
|
||||
Designer finds both, it can silently load the wrong one. Pick one channel per
|
||||
machine:
|
||||
|
||||
- Testing beta? Remove the regular **Visionary PacketAV** personal package
|
||||
first (below), then add the beta one.
|
||||
- Done testing? Do the reverse to go back to production.
|
||||
|
||||
A dedicated test computer is ideal if you have one.
|
||||
|
||||
## Add the beta package
|
||||
|
||||
1. Sign in at `https://library.qsys.com`, then **My Library → My Packages**.
|
||||
2. If the regular **Visionary PacketAV** entry is present, select it and
|
||||
**remove** it first (see "Leaving the beta" for why this is safe).
|
||||
3. Click **Add Personal Package** and enter:
|
||||
- **Asset name:** `Visionary PacketAV (Beta)`
|
||||
- **Package URL:** `https://vsplug.in/packetav-beta`
|
||||
4. Click **Add package**. You'll see the "Personal Package Added" confirmation.
|
||||
|
||||
## Install in Q-SYS Designer
|
||||
|
||||
1. Open **Q-SYS Designer** and sign in to the **same** Library account.
|
||||
2. **Tools → Show Asset Installer**.
|
||||
3. On **Available Assets**, find **Visionary-PacketAV-Suite-Beta** and click
|
||||
**Install**. Wait for **Install Status = Complete**.
|
||||
4. The plugins appear under **Visionary** in the schematic Plugins list, same
|
||||
as always. (Tip: check the plugin's **Help** page — beta builds are marked
|
||||
so you always know which channel you're on.)
|
||||
|
||||
## Getting new beta builds
|
||||
|
||||
When a new beta is published, the Asset Installer shows a higher **Latest
|
||||
Version** than your **Local Version** — click **Install** to update. If it isn't
|
||||
showing yet, click **Refresh**; if that's stale, remove and re-add the
|
||||
`https://vsplug.in/packetav-beta` entry (that forces a fresh read).
|
||||
|
||||
The plugins also tell you in-product: the **Software Update** line on the Help
|
||||
page checks the beta feed and reports when a newer beta is available.
|
||||
|
||||
## Reporting issues
|
||||
|
||||
Please include: plugin and version (from the Help page footer), Q-SYS Designer
|
||||
version, Core model, and what you expected vs. what happened. Send to
|
||||
`support@visionary-av.com` or use the links at `https://vsplug.in/resources`.
|
||||
|
||||
## Leaving the beta / going back to production
|
||||
|
||||
1. **My Packages** → remove the **Visionary PacketAV (Beta)** entry.
|
||||
2. Add the production package back: **Add Personal Package** →
|
||||
name `Visionary PacketAV`, URL `https://vsplug.in/packetav`.
|
||||
3. In **Asset Installer**, install the production suite over the beta. Because
|
||||
the plugin IDs match, your existing designs keep working — they just move
|
||||
back to the released version.
|
||||
|
||||
Removing a personal package only detaches the feed; it doesn't touch designs
|
||||
you've already built.
|
||||
@@ -0,0 +1,111 @@
|
||||
# Beta channel — one-time setup runbook
|
||||
|
||||
Goal: stand up a beta feed that Q-SYS testers can add without touching the
|
||||
production feed. You do this once; after that, publishing a beta is just the
|
||||
five steps in the README.
|
||||
|
||||
Placeholders used below (matching your production setup):
|
||||
- Gitea host: `3f4dzl.gitea.cloud`
|
||||
- Org: `visionary-plugins`
|
||||
- Beta package id: `Visionary-PacketAV-Suite-Beta`
|
||||
|
||||
---
|
||||
|
||||
## 1. Create the beta repo in Gitea
|
||||
|
||||
Same org as production (packages are owned by the org, so the beta package will
|
||||
sit alongside prod in the org's Packages list — that's expected).
|
||||
|
||||
1. In Gitea: **`visionary-plugins` org → New Repository**.
|
||||
2. Name it `qsys-plugin-registry-beta`. Set visibility however you like —
|
||||
**repo visibility does not affect the feed.** The package feed's anonymous
|
||||
readability comes from the **org** being Public, which it already is for
|
||||
prod. (If the org is Public, the beta package is anonymously readable too.)
|
||||
3. Push this scaffold to it:
|
||||
|
||||
```bash
|
||||
cd qsys-plugin-registry-beta
|
||||
git init -b main
|
||||
git add .
|
||||
git commit -m "Beta channel scaffold"
|
||||
git remote add origin https://3f4dzl.gitea.cloud/visionary-plugins/qsys-plugin-registry-beta.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## 2. Add the publish token
|
||||
|
||||
Same token as production works — a Gitea PAT with **package read/write** scope
|
||||
is org-wide, so it can publish `Visionary-PacketAV-Suite-Beta` too.
|
||||
|
||||
- Beta repo → **Settings → Actions → Secrets** → add **`PACKAGE_TOKEN`** with
|
||||
the same value you used for the prod repo.
|
||||
- Confirm a runner is available to the repo (Settings → Actions).
|
||||
|
||||
## 3. Publish a first beta build
|
||||
|
||||
1. Put a beta `.qplugx` set (or just copy the current prod content for a
|
||||
smoke test) + `icon.png` into
|
||||
`plugins/VisionarySolutions_Suite_Beta/content/`.
|
||||
2. Commit and push.
|
||||
3. Actions → **Publish Q-SYS plugins → Run workflow**.
|
||||
4. Verify the feed the same way as prod, anonymously:
|
||||
|
||||
```bash
|
||||
curl https://3f4dzl.gitea.cloud/api/packages/visionary-plugins/nuget/registration/Visionary-PacketAV-Suite-Beta/index.json
|
||||
```
|
||||
|
||||
You should see JSON listing the beta version(s).
|
||||
|
||||
## 4. Cloudflare short-links (301 redirects)
|
||||
|
||||
Add two aliases next to your existing `packetav` / `versions` rules. Point them
|
||||
at the beta package's registration URL and the beta versions file.
|
||||
|
||||
| Short link | 301 target |
|
||||
|---|---|
|
||||
| `vsplug.in/packetav-beta` | `https://3f4dzl.gitea.cloud/api/packages/visionary-plugins/nuget/registration/Visionary-PacketAV-Suite-Beta/index.json` |
|
||||
| `vsplug.in/versions-beta` | raw URL of `versions-beta.json` in your assets repo, e.g. `https://3f4dzl.gitea.cloud/visionary-plugins/assets/raw/branch/main/versions-beta.json` |
|
||||
|
||||
Then, in a browser (logged out), open `https://vsplug.in/packetav-beta` and
|
||||
confirm it lands on the registration JSON — QSC's backend must be able to
|
||||
follow the redirect, exactly as it does for the prod link.
|
||||
|
||||
## 5. Seed the beta update feed
|
||||
|
||||
Put `versions-beta.json` (in this repo's `docs/`) into the **assets** repo root
|
||||
so `vsplug.in/versions-beta` resolves. Keep its `package` value equal to the
|
||||
beta nuspec `<version>` and the `plugins` map equal to the beta build's plugin
|
||||
versions. Update it every time you publish a new beta.
|
||||
|
||||
## 6. Beta icon (optional but recommended)
|
||||
|
||||
The nuspec references `icon-beta.png` in the assets repo so beta is visually
|
||||
distinct in Asset Installer. Drop a beta-badged icon there, or change the
|
||||
`iconUrl` back to the prod `icon.png` if you'd rather not bother.
|
||||
|
||||
---
|
||||
|
||||
## Version lineage rules (same discipline as prod)
|
||||
|
||||
- Beta `<version>` is **3-part SemVer only** — a 4-part number makes the whole
|
||||
package vanish from Asset Installer.
|
||||
- Strictly ascending. The beta counter is **independent** of the prod counter;
|
||||
don't try to keep them in sync.
|
||||
- No prerelease `-beta` suffix on the number itself — Q-SYS filters those. The
|
||||
channel separation is the package id, not a version tag.
|
||||
|
||||
## Promotion flow (beta → production)
|
||||
|
||||
1. Copy the **same** `.qplugx` files from the beta `content/` into the prod
|
||||
repo's `content/`.
|
||||
2. Bump the **production** nuspec `<version>`.
|
||||
3. Commit/push and run the prod publish workflow.
|
||||
4. Update the prod `versions.json`.
|
||||
|
||||
Because the plugin files (and their PluginInfo.Ids) are byte-identical, this is
|
||||
a re-wrap, not a rebuild — what testers validated is exactly what ships.
|
||||
|
||||
## Retiring a beta build
|
||||
|
||||
Delete old beta versions from the org's Packages page anytime — beta has no
|
||||
field-install guarantees, so same-version delete/republish is fine here.
|
||||
@@ -0,0 +1,46 @@
|
||||
# update_check.lua — beta build changes
|
||||
|
||||
The update-check code is identical between channels except for **two
|
||||
constants** at the top, plus the per-build `PackageVersion` that already lives
|
||||
in each `.qplug`. Point beta builds at the beta feed and give them their own
|
||||
Notifications channel so a beta instance and a prod instance never elect each
|
||||
other (belt-and-suspenders — testers shouldn't be running both anyway).
|
||||
|
||||
Change these two lines in the beta build's `update_check.lua`:
|
||||
|
||||
```lua
|
||||
local CH = "VSI_PacketAV_Versions_Beta" -- was: VSI_PacketAV_Versions
|
||||
local VERSIONS_URL = "https://vsplug.in/versions-beta" -- was: https://vsplug.in/versions
|
||||
```
|
||||
|
||||
Everything else — leader election, gateway check, `applyVersions`, Check Now —
|
||||
is unchanged.
|
||||
|
||||
## PackageVersion
|
||||
|
||||
In each beta `.qplug`, set the `PackageVersion` constant to the **beta**
|
||||
package's version (the beta nuspec `<version>`), not the prod counter. That's
|
||||
what the update check compares against `versions-beta.json`'s `package` field to
|
||||
decide "a newer beta exists." Your `stamp_package_version.py` helper does this;
|
||||
run it against the beta build folder with the beta version.
|
||||
|
||||
## versions-beta.json shape
|
||||
|
||||
Same schema as prod `versions.json`, hosted at `vsplug.in/versions-beta`:
|
||||
|
||||
```json
|
||||
{"package":"1.0.0","plugins":{"Visionary~Decoder":"5.2.3","Visionary~Encoder":"5.2.3","Visionary~Router":"5.2.3","Visionary~IR Driver":"5.2.3"}}
|
||||
```
|
||||
|
||||
- `package` = beta nuspec `<version>` for the latest beta build.
|
||||
- `plugins` = each beta plugin's `PluginInfo.Version` (typically ahead of prod).
|
||||
|
||||
`annotate_qplugx.py` already emits a `versions.json` from a build folder; for
|
||||
beta, publish that output as `versions-beta.json` in the assets repo.
|
||||
|
||||
## Optional: mark the Help page as beta
|
||||
|
||||
Since the plugin IDs match prod, add a small "BETA CHANNEL" tag on the Help page
|
||||
(and/or set the beta plugins' `PluginInfo.Version` build field distinctly) so a
|
||||
tester glancing at the schematic always knows which channel is loaded. This is
|
||||
the same Software Update group from the prod mockup, just with a beta label.
|
||||
@@ -0,0 +1 @@
|
||||
{"package":"1.0.0","plugins":{"Visionary~Decoder":"5.2.3","Visionary~Encoder":"5.2.3","Visionary~Router":"5.2.3","Visionary~IR Driver":"5.2.3"}}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<!--
|
||||
BETA CHANNEL package manifest.
|
||||
Separate package identity from production so Q-SYS shows a distinct
|
||||
"My Packages" entry. The plugin files inside keep their real
|
||||
PluginInfo.Id values, so testers exercise the actual blocks - which is
|
||||
why a machine must NOT have both the prod and beta packages installed
|
||||
at once (same-Id files shadow each other in Designer).
|
||||
|
||||
Version is a 3-part SemVer delivery counter for the BETA lineage only,
|
||||
independent of the production package's counter. Q-SYS offers the
|
||||
highest version at the registration URL, so just bump this each beta.
|
||||
The <description> element is replaced from description.md at pack time.
|
||||
-->
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Visionary-PacketAV-Suite-Beta</id>
|
||||
<version>1.0.0</version>
|
||||
<title>Visionary PacketAV Suite (Beta)</title>
|
||||
<authors>Visionary Solutions Inc.</authors>
|
||||
<owners>Visionary Solutions Inc.</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<projectUrl>https://visionary-av.com/products/av-over-ip/</projectUrl>
|
||||
<icon>content/icon.png</icon>
|
||||
<iconUrl>https://3f4dzl.gitea.cloud/visionary-plugins/assets/raw/branch/main/icon-beta.png</iconUrl>
|
||||
<description>PLACEHOLDER - replaced from description.md at pack time</description>
|
||||
<summary>BETA test build of the Visionary PacketAV Suite. Pre-release - not for production designs.</summary>
|
||||
<releaseNotes>Beta channel. See the versions feed and your tester notes for what changed in this build.</releaseNotes>
|
||||
<copyright>2026 Visionary Solutions</copyright>
|
||||
<tags>Video Encoder Decoder Beta</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="content/**" target="content" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,15 @@
|
||||
BETA TEST BUILD — Visionary PacketAV Suite
|
||||
|
||||
This is a pre-release build for beta testers only. Do not use it in
|
||||
production designs, and do not install it on the same computer as the
|
||||
regular Visionary PacketAV Suite package — run one channel at a time.
|
||||
|
||||
A collection of Q-SYS plugins for managing, controlling and monitoring
|
||||
Encoders and Decoders in Visionary Solutions' PacketAV 4 Series and
|
||||
5 Series AV-over-IP devices, plus a Router plugin and an IR Driver plugin.
|
||||
|
||||
Q-SYS Designer Software v8.4 or later is required.
|
||||
|
||||
Note: 4 Series and 5 Series are not cross-compatible.
|
||||
|
||||
Beta resources and how to report issues: https://vsplug.in/resources
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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 ""
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user