mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 06:51:06 -04:00
feat(release): ship self-updating Velopack packages on GitHub Releases
Publish-Windows.ps1 now packs the smoke-tested folder with the pinned vpk CLI (.config/dotnet-tools.json, 1.2.0 to match the Velopack package) into velopack/: Setup.exe, Portable.zip, the full nupkg, releases.win.json and RELEASES, checked for the full package and the portable layout and hashed into OpenNest.vX.Y.Z.velopack.sha256. The release workflow uploads them with the candidate. With no feed URL set, installed copies now check GitHub Releases (full releases only) instead of skipping update checks; a URL in Options still selects a self-hosted feed. Deploy-OpenNest.ps1 uses the pinned vpk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cc16590881
commit
7fe4de4b6d
12 files changed
+108
-39
No files matched your search
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"vpk": {
|
||||
"version": "1.2.0",
|
||||
"commands": [
|
||||
"vpk"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,11 @@ jobs:
|
||||
path: |
|
||||
artifacts/*.zip
|
||||
artifacts/*.sha256
|
||||
artifacts/velopack/OpenNest-win-Setup.exe
|
||||
artifacts/velopack/OpenNest-win-Portable.zip
|
||||
artifacts/velopack/*-full.nupkg
|
||||
artifacts/velopack/releases.win.json
|
||||
artifacts/velopack/RELEASES
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
compression-level: 0
|
||||
|
||||
@@ -73,6 +73,6 @@ Read the relevant contract before changing its behavior:
|
||||
- [Cut-time estimates](docs/cut-time-estimates.md): simulator/Calculate Cut Time acceleration planner and placeholder machine limits
|
||||
- [Cincinnati CL](docs/cincinnati-post-output.md) and [CI Fiber](docs/cincinnati-ci-fiber-post-output.md)
|
||||
- [Fill verification](docs/performance/fill-verification.md): opt-in benchmarks, frozen oracles, Debug-only counters and predictor initialization. Zero Release counters do not prove work removal.
|
||||
- [Desktop auto-update](docs/auto-update.md): Velopack feed, `UpdateFeedUrl` setting and `Deploy-OpenNest.ps1`.
|
||||
- [Desktop auto-update](docs/auto-update.md): GitHub Releases by default, `UpdateFeedUrl` for a self-hosted feed, release Velopack assets and `Deploy-OpenNest.ps1`.
|
||||
- [Undo and Redo](docs/undo-redo.md): whole-nest history, the recording rule, what a restore rebuilds and what is excluded.
|
||||
- [Desktop help](docs/desktop-help.md): WebView2 help window, F1 topic IDs and the pinned help release both packaging scripts install.
|
||||
Generated
+1
-1
@@ -267,7 +267,7 @@
|
||||
this.updateGroupBox.Size = new System.Drawing.Size(684, 60);
|
||||
this.updateGroupBox.TabIndex = 3;
|
||||
this.updateGroupBox.TabStop = false;
|
||||
this.updateGroupBox.Text = "Auto-update feed URL (blank disables update checks; set per machine)";
|
||||
this.updateGroupBox.Text = "Auto-update feed URL (blank uses GitHub Releases; set per machine)";
|
||||
//
|
||||
// updateFeedUrlBox
|
||||
//
|
||||
|
||||
@@ -252,14 +252,14 @@ namespace OpenNest.Forms
|
||||
Settings.Default.UpdateFeedUrl = "";
|
||||
Settings.Default.Save();
|
||||
updateFeedUrlBox.Text = "";
|
||||
MessageBox.Show(this, "Update feed URL cleared. Update checks are disabled.",
|
||||
MessageBox.Show(this, "Update feed URL cleared. Updates will come from GitHub Releases after a restart.",
|
||||
"Update Feed URL", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NestStorageSettings.TryNormalizeServerUrl(text, out var url))
|
||||
{
|
||||
MessageBox.Show(this, "Enter a valid http or https feed URL, or clear the field to disable updates.",
|
||||
MessageBox.Show(this, "Enter a valid http or https feed URL, or clear the field to update from GitHub Releases.",
|
||||
"Update Feed URL", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -296,8 +296,8 @@ namespace OpenNest.Properties {
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
// Velopack update feed base URL (e.g. http://host:port). Blank disables
|
||||
// update checks; each machine sets its own value via Options, never committed.
|
||||
// Velopack update feed base URL (e.g. http://host:port). Blank uses GitHub
|
||||
// Releases; each machine sets its own value via Options, never committed.
|
||||
public string UpdateFeedUrl {
|
||||
get {
|
||||
return ((string)(this["UpdateFeedUrl"]));
|
||||
|
||||
@@ -15,14 +15,15 @@ namespace OpenNest
|
||||
/// <see cref="ApplyAndRestart"/> once the user agrees to restart.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The feed URL is never hard-coded: it is read from the per-machine
|
||||
/// <see cref="Settings.UpdateFeedUrl"/> setting (Options dialog), which is
|
||||
/// user-scoped and lives outside source control. A blank URL disables
|
||||
/// update checks entirely, which is the default for every install.
|
||||
/// A blank <see cref="Settings.UpdateFeedUrl"/> (the default) checks the
|
||||
/// public GitHub Releases for full, non-pre-release versions. A URL set in
|
||||
/// the Options dialog points this machine at a self-hosted feed instead;
|
||||
/// that URL is operator infrastructure and never lives in source control.
|
||||
/// </remarks>
|
||||
public sealed class UpdaterService : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(30);
|
||||
private const string GitHubRepositoryUrl = "https://github.com/ajisaacs/OpenNest";
|
||||
|
||||
private readonly UpdateManager _manager;
|
||||
private Timer _timer;
|
||||
@@ -36,10 +37,10 @@ namespace OpenNest
|
||||
|
||||
public UpdaterService(string feedUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(feedUrl))
|
||||
return;
|
||||
|
||||
_manager = new UpdateManager(new SimpleWebSource(feedUrl));
|
||||
IUpdateSource source = string.IsNullOrWhiteSpace(feedUrl)
|
||||
? new GithubSource(GitHubRepositoryUrl, null, false)
|
||||
: new SimpleWebSource(feedUrl);
|
||||
_manager = new UpdateManager(source);
|
||||
}
|
||||
|
||||
/// <summary>True once a downloaded update is waiting for <see cref="ApplyAndRestart"/>.</summary>
|
||||
|
||||
@@ -21,7 +21,7 @@ A Windows desktop application for CNC nesting — imports DXF drawings, arranges
|
||||
- Windows 10+ for the desktop app; the console, API, shared desktop library (`OpenNest.Desktop`) and most test projects build on Linux/macOS too. `OpenNest.Avalonia` is an early cross-platform desktop prototype, not yet a usable replacement.
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) to build from source.
|
||||
|
||||
Prebuilt Windows downloads are on [GitHub Releases](https://github.com/ajisaacs/OpenNest/releases), or [build from source](#build-test-run) to run the latest code. Release ZIPs are self-contained: extract the entire archive into a new folder and run `OpenNest.exe`; no separate .NET installation is needed. To update, close OpenNest and extract the new release into a new folder; ZIP releases do not auto-update (optional installer-based updates need a separately hosted [Velopack feed](docs/auto-update.md)). All nesting engines (Default, Rectangles, Irregular, Circles, FlatBar and the fill strategies) are built in; see [nesting engines](docs/nesting-engines.md).
|
||||
Prebuilt Windows downloads are on [GitHub Releases](https://github.com/ajisaacs/OpenNest/releases), or [build from source](#build-test-run) to run the latest code. Release ZIPs are self-contained: extract the entire archive into a new folder and run `OpenNest.exe`; no separate .NET installation is needed. For automatic updates, install with `OpenNest-win-Setup.exe` or extract `OpenNest-win-Portable.zip` instead: those copies download new full releases from GitHub in the background and restart only when you agree ([auto-update](docs/auto-update.md)). The plain ZIP does not auto-update; to update it, close OpenNest and extract the new release into a new folder. All nesting engines (Default, Rectangles, Irregular, Circles, FlatBar and the fill strategies) are built in; see [nesting engines](docs/nesting-engines.md).
|
||||
|
||||
## Build, Test, Run
|
||||
|
||||
|
||||
+34
-16
@@ -1,32 +1,49 @@
|
||||
# Desktop auto-update
|
||||
|
||||
The Windows desktop app (`OpenNest.exe`) can self-update via
|
||||
[Velopack](https://velopack.io): `OpenNest.UpdaterService` polls a static
|
||||
file feed every 30 minutes, downloads a newer release in the background, and
|
||||
[Velopack](https://velopack.io): `OpenNest.UpdaterService` polls its update
|
||||
source every 30 minutes, downloads a newer release in the background, and
|
||||
only restarts to apply it after the user confirms (a clickable status-bar
|
||||
item appears once a download finishes). It never force-restarts; an open,
|
||||
unsaved nest is never discarded automatically.
|
||||
|
||||
## Configuring the feed
|
||||
## Where updates come from
|
||||
|
||||
The feed URL is saved for the current Windows user
|
||||
(`OpenNest.Properties.Settings.UpdateFeedUrl`), set from **Tools > Options >
|
||||
Auto-update feed URL**. It is blank by default, which disables update checks
|
||||
entirely — this is intentional: the feed URL is operator infrastructure, not
|
||||
something that belongs in source control, so no default ships in the repo.
|
||||
Enter a feed's base URL (`http://host:port`) and restart the app to pick it
|
||||
up.
|
||||
By default an installed copy checks the public
|
||||
[GitHub Releases](https://github.com/ajisaacs/OpenNest/releases) for full,
|
||||
non-pre-release versions (`GithubSource`, unauthenticated: GitHub allows 60 API
|
||||
requests per hour per IP address, ample for a 30-minute poll but shared by every
|
||||
machine behind one address). Pre-releases are never offered.
|
||||
|
||||
Velopack only applies updates to an installed copy (`UpdateManager.IsInstalled`):
|
||||
one installed with the `Setup.exe` that `Deploy-OpenNest.ps1` produces. The ZIPs on
|
||||
GitHub Releases and ordinary build output are not Velopack installations and never
|
||||
auto-update, even with a feed URL configured.
|
||||
To use a self-hosted feed instead, set **Tools > Options > Auto-update feed URL**
|
||||
(`OpenNest.Properties.Settings.UpdateFeedUrl`, saved for the current Windows
|
||||
user) to the feed's base URL (`http://host:port`) and restart. A self-hosted
|
||||
feed URL is operator infrastructure and never belongs in source control; clearing
|
||||
the field returns to GitHub Releases.
|
||||
|
||||
Velopack only applies updates to a Velopack installation (`UpdateManager.IsInstalled`):
|
||||
a copy installed with `OpenNest-win-Setup.exe` or extracted from
|
||||
`OpenNest-win-Portable.zip`. The plain `OpenNest.vX.Y.Z.win-x64.zip` and ordinary
|
||||
build output contain no `Update.exe` and never auto-update; moving from one of
|
||||
them to an updating copy is a one-time manual install.
|
||||
|
||||
## Release packages
|
||||
|
||||
`scripts/Publish-Windows.ps1` packs the same smoke-tested folder as the plain ZIP
|
||||
with the `vpk` CLI pinned in `.config/dotnet-tools.json` (keep it equal to the
|
||||
`Velopack` package version), into `velopack/`: `OpenNest-win-Setup.exe`,
|
||||
`OpenNest-win-Portable.zip`, `OpenNest-X.Y.Z-full.nupkg`, `releases.win.json`
|
||||
and `RELEASES`, with their hashes in `OpenNest.vX.Y.Z.velopack.sha256`. Installed
|
||||
copies find an update through the `releases.win.json` and full package attached to
|
||||
the newest full GitHub Release, so all five must be uploaded with it (see
|
||||
[releasing](releasing.md#publish)). Packages are full only; delta packages would
|
||||
need the previous release's packages beside the output before `vpk pack`.
|
||||
|
||||
## Hosting a feed
|
||||
|
||||
Any static file server that can serve a `vpk pack` output directory works.
|
||||
`scripts/Deploy-OpenNest.ps1` builds a self-contained `win-x64` publish,
|
||||
packs it with the `vpk` CLI (`dotnet tool install -g vpk`), and `scp`s the
|
||||
packs it with the pinned `vpk` CLI, and `scp`s the
|
||||
result to a directory you provide:
|
||||
|
||||
```powershell
|
||||
@@ -36,4 +53,5 @@ result to a directory you provide:
|
||||
Bump `<Version>` in `OpenNest/OpenNest.csproj` before each run. This is
|
||||
independent of [the GitHub release process](releasing.md) and of
|
||||
`Publish-Windows.ps1` — the latter always overrides `<Version>` via
|
||||
`-p:Version`, so the two don't conflict.
|
||||
`-p:Version`, so the two don't conflict. Machines on a self-hosted feed only see
|
||||
versions pushed to that feed.
|
||||
+7
-1
@@ -27,7 +27,8 @@ pulls from GitHub every hour; it refuses pushes.
|
||||
(see [desktop help](desktop-help.md)), and `build-info.json` identifying the exact
|
||||
source commit and help version. The nesting engines
|
||||
are built into `OpenNest.Engine.dll`; no external engine repository or plug-in
|
||||
DLL is packaged. `scripts/ReleaseSmoke` loads the packaged engine assembly and
|
||||
DLL is packaged. Its `velopack/` folder holds the self-updating packages built
|
||||
from the same folder; see [desktop auto-update](auto-update.md#release-packages). `scripts/ReleaseSmoke` loads the packaged engine assembly and
|
||||
checks that every built-in engine instantiates from it, that the renamed
|
||||
`Opus55NestingEngine` selection resolves to Irregular, and that an unknown
|
||||
engine name is rejected.
|
||||
@@ -54,6 +55,11 @@ After the candidate passes, integrate the release tooling into the chosen branch
|
||||
create an annotated `vX.Y.Z` tag on the exact release commit and push it to
|
||||
GitHub. Use the successful **tag build's** artifacts
|
||||
for the GitHub Release; do not substitute packages built from another commit.
|
||||
Upload the plain ZIP, its `.sha256`, `OpenNest.vX.Y.Z.velopack.sha256` and all five
|
||||
files in `velopack/` as release assets, keeping their names. Installed copies update
|
||||
only from the newest full (non-pre-release) Release that carries
|
||||
`releases.win.json` and its `-full.nupkg`; a full Release without them leaves every
|
||||
installation on its current version.
|
||||
Verify the public asset names, sizes, download/checksum, and release status after
|
||||
upload. Do not overwrite an existing release/tag or asset silently.
|
||||
|
||||
|
||||
@@ -42,12 +42,10 @@ if ($csprojContent -notmatch '<Version>(?<v>[\d.]+)</Version>') { throw "Could n
|
||||
$Version = $Matches.v
|
||||
Write-Host "Packaging OpenNest v$Version"
|
||||
|
||||
# Ensure the vpk (Velopack) CLI is available
|
||||
if (-not (Get-Command vpk -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "Installing vpk CLI (dotnet tool)..."
|
||||
dotnet tool install -g vpk
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet tool install vpk failed" }
|
||||
}
|
||||
# The vpk (Velopack) CLI is pinned in .config/dotnet-tools.json to match the Velopack package
|
||||
Push-Location $RepoRoot
|
||||
try { & dotnet tool restore } finally { Pop-Location }
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet tool restore failed" }
|
||||
|
||||
$PublishDir = Join-Path $RepoRoot 'OpenNest\bin\publish-tmp'
|
||||
$ReleasesDir = Join-Path $RepoRoot 'OpenNest\bin\releases'
|
||||
@@ -61,7 +59,8 @@ $helpVersion = & (Join-Path $ScriptDir 'Install-Help.ps1') -Destination (Join-Pa
|
||||
Write-Host "Packaged help $helpVersion"
|
||||
|
||||
Write-Host "Packing Velopack release v$Version..."
|
||||
& vpk pack -u OpenNest -v $Version -p $PublishDir -e OpenNest.exe -o $ReleasesDir
|
||||
Push-Location $RepoRoot
|
||||
try { & dotnet tool run vpk pack -u OpenNest -v $Version -p $PublishDir -e OpenNest.exe -o $ReleasesDir } finally { Pop-Location }
|
||||
if ($LASTEXITCODE -ne 0) { throw "vpk pack failed with exit code $LASTEXITCODE" }
|
||||
|
||||
Write-Host "Pushing release feed to $FeedHost..."
|
||||
|
||||
@@ -114,5 +114,32 @@ try {
|
||||
$hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
"$hash $zipName" | Set-Content "$zip.sha256" -Encoding ascii
|
||||
Write-Host "Verified $zipName ($hash), source $commit"
|
||||
|
||||
# Velopack packages of the same verified folder: Setup.exe and the portable ZIP
|
||||
# self-update from GitHub Releases; the plain ZIP above never does.
|
||||
dotnet tool restore
|
||||
if ($LASTEXITCODE -ne 0) { throw 'dotnet tool restore failed.' }
|
||||
$velopack = Join-Path $output 'velopack'
|
||||
dotnet tool run vpk pack -u OpenNest -v $Version -p $package -e OpenNest.exe -o $velopack --packTitle OpenNest
|
||||
if ($LASTEXITCODE -ne 0) { throw 'vpk pack failed.' }
|
||||
|
||||
$assets = @('OpenNest-win-Setup.exe', 'OpenNest-win-Portable.zip', "OpenNest-$Version-full.nupkg", 'releases.win.json', 'RELEASES')
|
||||
foreach ($file in $assets) {
|
||||
if (-not (Test-Path (Join-Path $velopack $file) -PathType Leaf)) { throw "Velopack output missing $file" }
|
||||
}
|
||||
$feed = Get-Content (Join-Path $velopack 'releases.win.json') -Raw | ConvertFrom-Json
|
||||
if (@($feed.Assets | Where-Object { $_.Version -eq $Version -and $_.Type -eq 'Full' }).Count -ne 1) {
|
||||
throw "releases.win.json does not list the $Version full package."
|
||||
}
|
||||
$portable = Join-Path $output 'velopack-portable'
|
||||
Expand-Archive -Path (Join-Path $velopack 'OpenNest-win-Portable.zip') -DestinationPath $portable
|
||||
foreach ($file in @('Update.exe') + ($required | ForEach-Object { "current/$_" })) {
|
||||
if (-not (Test-Path (Join-Path $portable $file) -PathType Leaf)) { throw "Portable ZIP missing $file" }
|
||||
}
|
||||
$sums = foreach ($file in $assets) {
|
||||
"$((Get-FileHash (Join-Path $velopack $file) -Algorithm SHA256).Hash.ToLowerInvariant()) $file"
|
||||
}
|
||||
$sums | Set-Content (Join-Path $output "OpenNest.v$Version.velopack.sha256") -Encoding ascii
|
||||
Write-Host "Packed Velopack release $Version"
|
||||
}
|
||||
finally { Pop-Location }
|
||||
Reference in new issue
Block a user