mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-11 08:41:09 -05:00
feat(release): publish green master pushes as an opt-in development build
dev-build.yml runs after Cross-platform tests pass on a master push, packages that commit as X.Y.Z-dev.N (the minor after the newest vX.Y.Z tag, N the commit count) and replaces the single rolling dev-build pre-release. Only its publish job has release write access, and a run whose commit is no longer master's head publishes nothing. Tools > Options > Include development builds lets an installed copy take that pre-release; everyone else still sees full releases only. Publish-Windows.ps1 accepts -dev.N versions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b4c1f51d0
commit
bc15c362e1
10 files changed
+196
-12
No files matched your search
@@ -0,0 +1,128 @@
|
||||
name: Development build
|
||||
|
||||
# Every master push whose cross-platform tests pass becomes the rolling
|
||||
# `dev-build` pre-release: X.Y.Z-dev.N, where X.Y.Z is the minor after the
|
||||
# newest release tag and N the commit count. Installed copies take it only
|
||||
# with Tools > Options > Include development builds.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Cross-platform tests']
|
||||
types: [completed]
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: dev-build
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
package:
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 30
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
env:
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: '1'
|
||||
DOTNET_NOLOGO: '1'
|
||||
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
- name: Resolve development version
|
||||
id: version
|
||||
run: |
|
||||
$releases = git tag --list 'v*' | Where-Object { $_ -match '^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$' } |
|
||||
ForEach-Object { [version]$_.Substring(1) } | Sort-Object
|
||||
if (-not $releases) { throw 'No vX.Y.Z release tag found.' }
|
||||
$latest = @($releases)[-1]
|
||||
$count = (git rev-list --count $env:SOURCE_SHA).Trim()
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Cannot count commits.' }
|
||||
$version = "$($latest.Major).$($latest.Minor + 1).0-dev.$count"
|
||||
Write-Host "Development version $version (after v$latest)"
|
||||
"version=$version" >> $env:GITHUB_OUTPUT
|
||||
- name: Publish and verify Windows package
|
||||
run: ./scripts/Publish-Windows.ps1 -Version ${{ steps.version.outputs.version }}
|
||||
- name: Upload development build
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: OpenNest-dev-build
|
||||
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: 7
|
||||
compression-level: 0
|
||||
|
||||
publish:
|
||||
needs: package
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
VERSION: ${{ needs.package.outputs.version }}
|
||||
steps:
|
||||
- name: Skip if master has moved on
|
||||
id: current
|
||||
run: |
|
||||
head=$(gh api "repos/$GH_REPO/commits/master" --jq .sha)
|
||||
if [ "$head" != "$SOURCE_SHA" ]; then
|
||||
echo "master is at $head; its own run publishes the newer build."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Download development build
|
||||
if: steps.current.outputs.skip != 'true'
|
||||
run: gh run download "$GITHUB_RUN_ID" -n OpenNest-dev-build -D dev
|
||||
- name: Verify checksums
|
||||
if: steps.current.outputs.skip != 'true'
|
||||
working-directory: dev
|
||||
run: |
|
||||
sha256sum -c ./*.zip.sha256
|
||||
(cd velopack && sha256sum -c "../OpenNest.v$VERSION.velopack.sha256")
|
||||
- name: Replace the dev-build pre-release
|
||||
if: steps.current.outputs.skip != 'true'
|
||||
working-directory: dev
|
||||
run: |
|
||||
if gh release view dev-build >/dev/null 2>&1; then
|
||||
gh release delete dev-build --cleanup-tag --yes
|
||||
fi
|
||||
cat > notes.md <<EOF
|
||||
Automatic development build of master at $SOURCE_SHA.
|
||||
|
||||
It may contain unfinished features. Installed copies receive it only with
|
||||
**Tools > Options > Include development builds**; full releases are unaffected.
|
||||
EOF
|
||||
gh release create dev-build ./*.zip ./*.sha256 velopack/* \
|
||||
--target "$SOURCE_SHA" --prerelease --latest=false \
|
||||
--title "OpenNest $VERSION (development build, ${SOURCE_SHA:0:8})" --notes-file notes.md
|
||||
- name: Read back the pre-release
|
||||
if: steps.current.outputs.skip != 'true'
|
||||
run: |
|
||||
gh release view dev-build --json isPrerelease,isDraft,tagName,assets \
|
||||
--jq '"prerelease=\(.isPrerelease) draft=\(.isDraft) assets=\(.assets|length)"' | tee readback.txt
|
||||
grep -qx 'prerelease=true draft=false assets=8' readback.txt
|
||||
[ "$(gh api "repos/$GH_REPO/git/ref/tags/dev-build" --jq .object.sha)" = "$SOURCE_SHA" ]
|
||||
@@ -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): GitHub Releases by default, `UpdateFeedUrl` for a self-hosted feed, release Velopack assets and `Deploy-OpenNest.ps1`.
|
||||
- [Desktop auto-update](docs/auto-update.md): GitHub Releases by default, opt-in rolling `dev-build` pre-release from green master pushes, `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
+16
-3
@@ -49,6 +49,7 @@
|
||||
this.updateGroupBox = new System.Windows.Forms.GroupBox();
|
||||
this.updateFeedUrlBox = new System.Windows.Forms.TextBox();
|
||||
this.setUpdateFeedButton = new System.Windows.Forms.Button();
|
||||
this.includeDevBuildsBox = new System.Windows.Forms.CheckBox();
|
||||
this.ledgerGroupBox = new System.Windows.Forms.GroupBox();
|
||||
this.ledgerUrlLabel = new System.Windows.Forms.Label();
|
||||
this.ledgerUrlBox = new System.Windows.Forms.TextBox();
|
||||
@@ -203,7 +204,7 @@
|
||||
// strategyGroupBox
|
||||
//
|
||||
this.strategyGroupBox.Controls.Add(this.strategyGrid);
|
||||
this.strategyGroupBox.Location = new System.Drawing.Point(12, 457);
|
||||
this.strategyGroupBox.Location = new System.Drawing.Point(12, 485);
|
||||
this.strategyGroupBox.Name = "strategyGroupBox";
|
||||
this.strategyGroupBox.Size = new System.Drawing.Size(684, 180);
|
||||
this.strategyGroupBox.TabIndex = 2;
|
||||
@@ -262,9 +263,10 @@
|
||||
//
|
||||
this.updateGroupBox.Controls.Add(this.updateFeedUrlBox);
|
||||
this.updateGroupBox.Controls.Add(this.setUpdateFeedButton);
|
||||
this.updateGroupBox.Controls.Add(this.includeDevBuildsBox);
|
||||
this.updateGroupBox.Location = new System.Drawing.Point(12, 391);
|
||||
this.updateGroupBox.Name = "updateGroupBox";
|
||||
this.updateGroupBox.Size = new System.Drawing.Size(684, 60);
|
||||
this.updateGroupBox.Size = new System.Drawing.Size(684, 88);
|
||||
this.updateGroupBox.TabIndex = 3;
|
||||
this.updateGroupBox.TabStop = false;
|
||||
this.updateGroupBox.Text = "Auto-update feed URL (blank uses GitHub Releases; set per machine)";
|
||||
@@ -287,6 +289,16 @@
|
||||
this.setUpdateFeedButton.UseVisualStyleBackColor = true;
|
||||
this.setUpdateFeedButton.Click += new System.EventHandler(this.SetUpdateFeed_Click);
|
||||
//
|
||||
// includeDevBuildsBox
|
||||
//
|
||||
this.includeDevBuildsBox.AutoSize = true;
|
||||
this.includeDevBuildsBox.Location = new System.Drawing.Point(12, 58);
|
||||
this.includeDevBuildsBox.Name = "includeDevBuildsBox";
|
||||
this.includeDevBuildsBox.Size = new System.Drawing.Size(460, 20);
|
||||
this.includeDevBuildsBox.TabIndex = 2;
|
||||
this.includeDevBuildsBox.Text = "Include development builds (latest master; takes effect after restart)";
|
||||
this.includeDevBuildsBox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// ledgerGroupBox
|
||||
//
|
||||
this.ledgerGroupBox.Controls.Add(this.ledgerUrlLabel);
|
||||
@@ -362,7 +374,7 @@
|
||||
this.AcceptButton = this.saveButton;
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(708, 697);
|
||||
this.ClientSize = new System.Drawing.Size(708, 725);
|
||||
this.Controls.Add(this.strategyGroupBox);
|
||||
this.Controls.Add(this.updateGroupBox);
|
||||
this.Controls.Add(this.ledgerGroupBox);
|
||||
@@ -415,6 +427,7 @@
|
||||
private System.Windows.Forms.GroupBox updateGroupBox;
|
||||
private System.Windows.Forms.TextBox updateFeedUrlBox;
|
||||
private System.Windows.Forms.Button setUpdateFeedButton;
|
||||
private System.Windows.Forms.CheckBox includeDevBuildsBox;
|
||||
private System.Windows.Forms.GroupBox ledgerGroupBox;
|
||||
private System.Windows.Forms.Label ledgerUrlLabel;
|
||||
private System.Windows.Forms.TextBox ledgerUrlBox;
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace OpenNest.Forms
|
||||
{
|
||||
serverUrlBox.Text = NestStorage.Settings.ServerUrl;
|
||||
updateFeedUrlBox.Text = Settings.Default.UpdateFeedUrl;
|
||||
includeDevBuildsBox.Checked = Settings.Default.UpdateIncludeDevBuilds;
|
||||
ledgerUrlBox.Text = MaterialLedgerStore.Settings.BaseUrl;
|
||||
ledgerKeyBox.Text = MaterialLedgerStore.Settings.ApiKey;
|
||||
checkBox1.Checked = Settings.Default.CreateNewNestOnOpen;
|
||||
@@ -105,6 +106,7 @@ namespace OpenNest.Forms
|
||||
private void SaveSettings()
|
||||
{
|
||||
Settings.Default.CreateNewNestOnOpen = checkBox1.Checked;
|
||||
Settings.Default.UpdateIncludeDevBuilds = includeDevBuildsBox.Checked;
|
||||
Settings.Default.AutoSizePlateFactor = (double)numericUpDown1.Value;
|
||||
Settings.Default.CuttingPlanEffort = (double)planningEffortBox.Value;
|
||||
Settings.Default.ActiveColorScheme =
|
||||
|
||||
Generated
+13
@@ -307,6 +307,19 @@ namespace OpenNest.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
// Also offer the rolling dev-build pre-release from GitHub Releases.
|
||||
public bool UpdateIncludeDevBuilds {
|
||||
get {
|
||||
return ((bool)(this["UpdateIncludeDevBuilds"]));
|
||||
}
|
||||
set {
|
||||
this["UpdateIncludeDevBuilds"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("Laser")]
|
||||
|
||||
@@ -74,6 +74,9 @@
|
||||
<Setting Name="UpdateFeedUrl" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="UpdateIncludeDevBuilds" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="DrawingLibraryCutType" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">Laser</Value>
|
||||
</Setting>
|
||||
|
||||
@@ -16,7 +16,8 @@ namespace OpenNest
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A blank <see cref="Settings.UpdateFeedUrl"/> (the default) checks the
|
||||
/// public GitHub Releases for full, non-pre-release versions. A URL set in
|
||||
/// public GitHub Releases for full versions, plus the rolling development
|
||||
/// pre-release when <see cref="Settings.UpdateIncludeDevBuilds"/> is set. 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>
|
||||
@@ -33,12 +34,12 @@ namespace OpenNest
|
||||
public event Action<string> UpdateReady;
|
||||
|
||||
public UpdaterService()
|
||||
: this(Settings.Default.UpdateFeedUrl) { }
|
||||
: this(Settings.Default.UpdateFeedUrl, Settings.Default.UpdateIncludeDevBuilds) { }
|
||||
|
||||
public UpdaterService(string feedUrl)
|
||||
public UpdaterService(string feedUrl, bool includeDevBuilds)
|
||||
{
|
||||
IUpdateSource source = string.IsNullOrWhiteSpace(feedUrl)
|
||||
? new GithubSource(GitHubRepositoryUrl, null, false)
|
||||
? new GithubSource(GitHubRepositoryUrl, null, includeDevBuilds)
|
||||
: new SimpleWebSource(feedUrl);
|
||||
_manager = new UpdateManager(source);
|
||||
}
|
||||
|
||||
+21
-1
@@ -13,7 +13,8 @@ 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.
|
||||
machine behind one address). Pre-releases are offered only as the development
|
||||
build below.
|
||||
|
||||
To use a self-hosted feed instead, set **Tools > Options > Auto-update feed URL**
|
||||
(`OpenNest.Properties.Settings.UpdateFeedUrl`, saved for the current Windows
|
||||
@@ -39,6 +40,25 @@ 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`.
|
||||
|
||||
## Development builds
|
||||
|
||||
`.github/workflows/dev-build.yml` runs after every master push whose
|
||||
`Cross-platform tests` pass (that workflow includes the Windows desktop tests).
|
||||
It packages that commit with `Publish-Windows.ps1` as `X.Y.Z-dev.N`, where
|
||||
`X.Y.Z` is the minor after the newest `vX.Y.Z` tag and `N` the commit count, so
|
||||
each build outranks the last and the next full release outranks them all. It then
|
||||
replaces the single rolling `dev-build` pre-release (tag `dev-build`, never marked
|
||||
Latest) with the same eight assets as a release. A run whose commit is no longer
|
||||
master's head publishes nothing; the newer run does.
|
||||
|
||||
Installed copies take it only with **Tools > Options > Include development
|
||||
builds** (`UpdateIncludeDevBuilds`, restart to apply); everyone else sees full
|
||||
releases only. Velopack reads the ten newest GitHub releases, so keep a single
|
||||
rolling pre-release: a pile of them could push the newest full release out of
|
||||
that window. Turning the option off leaves a copy on its development build until
|
||||
a higher full release appears. The workflow's publish job is the only automation
|
||||
with release write access; it is limited to this pre-release.
|
||||
|
||||
## Hosting a feed
|
||||
|
||||
Any static file server that can serve a `vpk pack` output directory works.
|
||||
|
||||
+3
-1
@@ -34,7 +34,9 @@ pulls from GitHub every hour; it refuses pushes.
|
||||
engine name is rejected.
|
||||
|
||||
The workflow has read-only repository permissions and does **not** publish
|
||||
releases. Once present on the default branch, it can also be dispatched manually
|
||||
releases. The separate [development build](auto-update.md#development-builds)
|
||||
workflow does replace the rolling `dev-build` pre-release after each green master
|
||||
push (owner-approved); it is not a release and never touches `vX.Y.Z` tags. Once present on the default branch, it can also be dispatched manually
|
||||
with an `X.Y.Z` version. Running/dispatching via a PAT needs suitable Actions
|
||||
permissions; public run metadata alone does not prove dispatch access.
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$')]
|
||||
# X.Y.Z for releases; X.Y.Z-dev.N for the automatic development build.
|
||||
[ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-dev\.(0|[1-9]\d*))?$')]
|
||||
[string] $Version,
|
||||
[string] $OutputDirectory = 'artifacts',
|
||||
# A built opennest-help site/ folder to package instead of the release pinned in help.json.
|
||||
@@ -67,7 +68,8 @@ try {
|
||||
if (-not (Test-Path (Join-Path $package $file) -PathType Leaf)) { throw "Package missing $file" }
|
||||
}
|
||||
$assemblyVersion = [Reflection.AssemblyName]::GetAssemblyName((Join-Path $package 'OpenNest.dll')).Version
|
||||
if ($assemblyVersion.ToString() -ne "$Version.0") { throw "Wrong assembly version: $assemblyVersion" }
|
||||
$numericVersion = $Version -replace '-.*$', ''
|
||||
if ($assemblyVersion.ToString() -ne "$numericVersion.0") { throw "Wrong assembly version: $assemblyVersion" }
|
||||
$runtime = Get-Content (Join-Path $package 'OpenNest.runtimeconfig.json') -Raw | ConvertFrom-Json
|
||||
if (-not $runtime.runtimeOptions.includedFrameworks) { throw 'Package is not self-contained.' }
|
||||
|
||||
|
||||
Reference in new issue
Block a user