mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-26 22:01:06 -04:00
feat(desktop): add WebView2 help window with F1 topics and pinned help packaging
Help > OpenNest Help (F1) and context F1 in Auto Nest and the CAD Converter open the bundled opennest-help site in a single WebView2 window on its own UI thread. Both packaging scripts install the opennest-help release pinned in scripts/help.json, verified by SHA-256. The missing lead-in finding now says the contour is missing lead-in geometry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
00cb639c8e
commit
ed92c37845
15 files changed
+421
-7
No files matched your search
@@ -7,7 +7,7 @@ OpenNest is a .NET 8 Windows CNC-nesting application with cross-platform librari
|
||||
|
||||
- Prefer Roslyn Bridge MCP for symbols, references and diagnostics when available; fall back to text search.
|
||||
- Use `var` for locals and namespaces matching project directories. Follow `.editorconfig`; format only changed C# files with `dotnet format OpenNest.sln --include <paths>`, then repeat with `--verify-no-changes`. On Linux, prefix both commands with `EnableWindowsTargeting=true`.
|
||||
- Keep instructions concise: commands, boundaries and non-obvious safeguards, not class inventories or session history. Update affected instructions and user-facing docs with behavior/build changes; put detailed contracts in `docs/`.
|
||||
- Keep instructions concise: commands, boundaries and non-obvious safeguards, not class inventories or session history. Update affected instructions and user-facing docs with behavior/build changes; put detailed contracts in `docs/`. User guides and their screenshots live in the separate `ajisaacs/opennest-help` repo: update or recapture them when a UI change alters a documented screen, and never rename a `HelpTopics` ID.
|
||||
- Never commit design specs, implementation plans, progress notes or temporary benchmark reports. Keep working records under local, ignored `.hermes/plans/` or `.hermes/progress/`; retain reusable verification procedures in `docs/`.
|
||||
- Keep vendor manuals/full-text extracts out of source control unless redistribution is authorized. Write project-specific behavior summaries with citations, separating controller rules, machine macros and unconfirmed behavior.
|
||||
|
||||
@@ -68,3 +68,4 @@ Read the relevant contract before changing its behavior:
|
||||
- [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 help](docs/desktop-help.md): WebView2 help window, F1 topic IDs and the pinned help release both packaging scripts install.
|
||||
@@ -95,7 +95,7 @@ public sealed class ReleasedContourState
|
||||
Finish();
|
||||
if (reportMissingLeadIns && !cutoff && !hasLead)
|
||||
Add(new(PostVerificationKind.MissingLeadIn, plate, part, null,
|
||||
"A break cut has no nonzero placed lead-in motion.")
|
||||
"A break cut is missing lead-in geometry.")
|
||||
{ Location = move.Curve.Start }, owned);
|
||||
inBreakCut = true;
|
||||
hasLead = false;
|
||||
@@ -179,7 +179,7 @@ public sealed class ReleasedContourState
|
||||
contourNumber++;
|
||||
if (reportMissingLeadIns && !cutoff && !hasLead)
|
||||
Add(new(PostVerificationKind.MissingLeadIn, plate, part, null,
|
||||
$"Cutting contour {contourNumber} has no nonzero placed lead-in motion.")
|
||||
$"Cutting contour {contourNumber} is missing lead-in geometry.")
|
||||
{ Location = move.Curve.Start }, owned);
|
||||
hasLead = false;
|
||||
// A rapid can pause/reposition without leaving any material gap.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using OpenNest.Help;
|
||||
|
||||
namespace OpenNest.WinForms.Tests;
|
||||
|
||||
public class HelpTopicsTests : IDisposable
|
||||
{
|
||||
private readonly string directory = Directory.CreateTempSubdirectory("opennest-help-").FullName;
|
||||
|
||||
public void Dispose() => Directory.Delete(directory, recursive: true);
|
||||
|
||||
private void WriteTopics(string json) => File.WriteAllText(Path.Combine(directory, "topics.json"), json);
|
||||
|
||||
[Fact]
|
||||
public void MapsTopicToItsPageAndAnchor()
|
||||
{
|
||||
WriteTopics("""{ "topics": { "home": "index.html", "auto-nest": "first-nest.html#run-auto-nest" } }""");
|
||||
|
||||
Assert.Equal("first-nest.html#run-auto-nest", HelpTopics.ResolvePage(directory, HelpTopics.AutoNest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownTopicOpensTheHomeTopic()
|
||||
{
|
||||
WriteTopics("""{ "topics": { "home": "welcome.html" } }""");
|
||||
|
||||
Assert.Equal("welcome.html", HelpTopics.ResolvePage(directory, "not-a-topic"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("not json")]
|
||||
[InlineData("""{ "pages": {} }""")]
|
||||
public void MissingOrUnreadableTopicsFallBackToIndex(string json)
|
||||
{
|
||||
if (json != null)
|
||||
WriteTopics(json);
|
||||
|
||||
Assert.Equal("index.html", HelpTopics.ResolvePage(directory, HelpTopics.Post));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Help;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace OpenNest.Forms
|
||||
LoadDrawings(nest);
|
||||
LoadDefaultPlateOptions();
|
||||
SetPlateOptimizerVisible(false);
|
||||
HelpTopics.Attach(this, HelpTopics.AutoNest);
|
||||
|
||||
partsGrid.DataError += PartsGrid_DataError;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ using OpenNest.CNC;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Help;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.IO.Bending;
|
||||
using OpenNest.TabbedCutouts;
|
||||
@@ -44,6 +45,7 @@ namespace OpenNest.Forms
|
||||
public CadConverterForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
HelpTopics.Attach(this, HelpTopics.ImportDrawings);
|
||||
|
||||
fileList.SelectedIndexChanged += OnFileSelected;
|
||||
filterPanel.FilterChanged += OnFilterChanged;
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Windows.Forms;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Engine.Jobs;
|
||||
using OpenNest.Forms.Commands;
|
||||
using OpenNest.Help;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Properties;
|
||||
|
||||
@@ -119,6 +120,7 @@ namespace OpenNest.Forms
|
||||
mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExport), exportNestMenu);
|
||||
mnuFile.DropDownItems.Insert(mnuFile.DropDownItems.IndexOf(mnuFileExit), storageModeMenu);
|
||||
InitializeAlignMenu();
|
||||
InitializeHelpMenu();
|
||||
UpdateOverlapMenu();
|
||||
MainWindowSettings.Load(this);
|
||||
|
||||
@@ -160,6 +162,17 @@ namespace OpenNest.Forms
|
||||
|
||||
private UpdaterStatus updaterStatus;
|
||||
|
||||
private void InitializeHelpMenu()
|
||||
{
|
||||
var contents = new ToolStripMenuItem("OpenNest &Help") { ShortcutKeys = Keys.F1 };
|
||||
contents.Click += (_, _) => HelpTopics.Show(this, HelpTopics.Home);
|
||||
var firstNest = new ToolStripMenuItem("&Your First Nest");
|
||||
firstNest.Click += (_, _) => HelpTopics.Show(this, HelpTopics.FirstNest);
|
||||
var help = new ToolStripMenuItem("&Help");
|
||||
help.DropDownItems.AddRange(new ToolStripItem[] { contents, firstNest });
|
||||
menuStrip1.Items.Add(help);
|
||||
}
|
||||
|
||||
private static ToolStripButton CreateActionButton(string name, string text, Image image,
|
||||
EventHandler click)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Microsoft.Web.WebView2.WinForms;
|
||||
|
||||
namespace OpenNest.Help
|
||||
{
|
||||
/// <summary>
|
||||
/// Single help window hosting the bundled HTML in WebView2. The help folder is served
|
||||
/// from a private virtual host so the site's search and scripts run as on a web server.
|
||||
/// The window runs its own UI thread, so modal dialogs in the app never disable it:
|
||||
/// F1 in a dialog opens help that can be scrolled and searched while the dialog is open.
|
||||
/// </summary>
|
||||
public sealed class HelpForm : Form
|
||||
{
|
||||
private const string Host = "help.opennest.local";
|
||||
private static readonly string Origin = $"https://{Host}/";
|
||||
private static readonly TimeSpan StartTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
private static readonly object Sync = new();
|
||||
private static HelpForm instance;
|
||||
|
||||
private readonly WebView2 browser = new() { Dock = DockStyle.Fill };
|
||||
private string pendingPage;
|
||||
private bool ready;
|
||||
|
||||
private HelpForm(string page)
|
||||
{
|
||||
pendingPage = page;
|
||||
Text = "OpenNest Help";
|
||||
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
var area = Screen.FromPoint(Cursor.Position).WorkingArea;
|
||||
Size = new Size(System.Math.Min(1100, area.Width), System.Math.Min(900, area.Height));
|
||||
Location = new Point(area.Right - Width, area.Top);
|
||||
Controls.Add(browser);
|
||||
}
|
||||
|
||||
/// <summary>Shows <paramref name="page"/> (relative to the help folder) in the help window.</summary>
|
||||
public static void ShowPage(string page)
|
||||
{
|
||||
lock (Sync)
|
||||
{
|
||||
var open = instance;
|
||||
if (open != null && !open.IsDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
open.BeginInvoke(() => open.Reveal(page));
|
||||
return;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Closed between the check and the call: start a new window below.
|
||||
}
|
||||
}
|
||||
|
||||
using var started = new ManualResetEventSlim();
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
var form = new HelpForm(page);
|
||||
form.HandleCreated += (_, _) => started.Set();
|
||||
instance = form;
|
||||
Application.Run(form);
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "OpenNest Help",
|
||||
};
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
started.Wait(StartTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private void Reveal(string page)
|
||||
{
|
||||
if (WindowState == FormWindowState.Minimized)
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
Navigate(page);
|
||||
}
|
||||
|
||||
protected override async void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
try
|
||||
{
|
||||
var dataFolder = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"OpenNest", "WebView2");
|
||||
var environment = await CoreWebView2Environment.CreateAsync(null, dataFolder);
|
||||
await browser.EnsureCoreWebView2Async(environment);
|
||||
}
|
||||
catch (Exception ex) when (ex is WebView2RuntimeNotFoundException or COMException or IOException
|
||||
or UnauthorizedAccessException)
|
||||
{
|
||||
MessageBox.Show(this,
|
||||
"OpenNest Help needs the Microsoft Edge WebView2 Runtime, which could not be started.\n\n"
|
||||
+ ex.Message,
|
||||
"OpenNest Help", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var core = browser.CoreWebView2;
|
||||
core.SetVirtualHostNameToFolderMapping(Host, HelpTopics.Directory,
|
||||
CoreWebView2HostResourceAccessKind.DenyCors);
|
||||
core.Settings.AreDevToolsEnabled = false;
|
||||
core.Settings.IsStatusBarEnabled = false;
|
||||
core.NavigationStarting += Core_NavigationStarting;
|
||||
core.NewWindowRequested += Core_NewWindowRequested;
|
||||
// Site page titles already end in "OpenNest Help".
|
||||
core.DocumentTitleChanged += (_, _) => Text = core.DocumentTitle;
|
||||
|
||||
ready = true;
|
||||
if (pendingPage != null)
|
||||
Navigate(pendingPage);
|
||||
}
|
||||
|
||||
private void Navigate(string page)
|
||||
{
|
||||
if (!ready)
|
||||
{
|
||||
pendingPage = page;
|
||||
return;
|
||||
}
|
||||
|
||||
pendingPage = null;
|
||||
browser.CoreWebView2.Navigate(Origin + page.TrimStart('/'));
|
||||
}
|
||||
|
||||
// Help pages stay in this window; anything off-site opens in the user's browser.
|
||||
private static void Core_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
|
||||
{
|
||||
if (IsHelpPage(e.Uri))
|
||||
return;
|
||||
e.Cancel = true;
|
||||
OpenExternally(e.Uri);
|
||||
}
|
||||
|
||||
private void Core_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
|
||||
{
|
||||
e.Handled = true;
|
||||
if (IsHelpPage(e.Uri))
|
||||
browser.CoreWebView2.Navigate(e.Uri);
|
||||
else
|
||||
OpenExternally(e.Uri);
|
||||
}
|
||||
|
||||
private static bool IsHelpPage(string uri) =>
|
||||
uri.StartsWith(Origin, StringComparison.OrdinalIgnoreCase) || uri.StartsWith("about:", StringComparison.Ordinal);
|
||||
|
||||
private static void OpenExternally(string uri)
|
||||
{
|
||||
if (!Uri.TryCreate(uri, UriKind.Absolute, out var parsed)
|
||||
|| (parsed.Scheme != Uri.UriSchemeHttps && parsed.Scheme != Uri.UriSchemeHttp))
|
||||
return;
|
||||
Process.Start(new ProcessStartInfo(parsed.AbsoluteUri) { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.Help
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens the bundled help (built from the ajisaacs/opennest-help repository) at a topic.
|
||||
/// Topic IDs are a stable contract: the help's <c>topics.json</c> maps each ID to a page,
|
||||
/// so pages can move without app changes. Never rename an ID the app uses.
|
||||
/// </summary>
|
||||
public static class HelpTopics
|
||||
{
|
||||
public const string Home = "home";
|
||||
public const string FirstNest = "first-nest";
|
||||
public const string ImportDrawings = "import-drawings";
|
||||
public const string AutoNest = "auto-nest";
|
||||
public const string Post = "post";
|
||||
|
||||
/// <summary>Overrides the help folder, e.g. a local opennest-help <c>site/</c> build.</summary>
|
||||
public const string DirectoryVariable = "OPENNEST_HELP_DIR";
|
||||
|
||||
public static string Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
var overridden = Environment.GetEnvironmentVariable(DirectoryVariable);
|
||||
return string.IsNullOrWhiteSpace(overridden)
|
||||
? Path.Combine(Application.StartupPath, "Help")
|
||||
: overridden;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInstalled => File.Exists(Path.Combine(Directory, "index.html"));
|
||||
|
||||
/// <summary>Opens <paramref name="topic"/> when F1 is pressed anywhere in <paramref name="form"/>.</summary>
|
||||
public static void Attach(Form form, string topic)
|
||||
{
|
||||
form.HelpRequested += (_, e) =>
|
||||
{
|
||||
e.Handled = true;
|
||||
Show(form, topic);
|
||||
};
|
||||
}
|
||||
|
||||
public static void Show(IWin32Window owner, string topic)
|
||||
{
|
||||
if (!IsInstalled)
|
||||
{
|
||||
MessageBox.Show(owner,
|
||||
$"Help is not installed. Expected it in:\n{Directory}",
|
||||
"OpenNest Help", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
HelpForm.ShowPage(ResolvePage(Directory, topic));
|
||||
}
|
||||
|
||||
/// <summary>Maps a topic ID to a site-relative page, falling back to the home page.</summary>
|
||||
public static string ResolvePage(string helpDirectory, string topic)
|
||||
{
|
||||
var topics = LoadTopics(helpDirectory);
|
||||
if (topics.TryGetValue(topic, out var page))
|
||||
return page;
|
||||
return topics.TryGetValue(Home, out var home) ? home : "index.html";
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> LoadTopics(string helpDirectory)
|
||||
{
|
||||
var path = Path.Combine(helpDirectory, "topics.json");
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(File.ReadAllText(path));
|
||||
var topics = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var property in document.RootElement.GetProperty("topics").EnumerateObject())
|
||||
topics[property.Name] = property.Value.GetString();
|
||||
return topics;
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or JsonException or KeyNotFoundException
|
||||
or InvalidOperationException or UnauthorizedAccessException)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
<ProjectReference Include="..\Posts\OpenNest.Posts.Cincinnati\OpenNest.Posts.Cincinnati.csproj" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="..\Posts\OpenNest.Posts.CincinnatiCIFiber\OpenNest.Posts.CincinnatiCIFiber.csproj" ReferenceOutputAssembly="false" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<PackageReference Include="Velopack" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Desktop help
|
||||
|
||||
User guides live in [ajisaacs/opennest-help](https://github.com/ajisaacs/opennest-help):
|
||||
Markdown pages plus annotated screenshots, built with MkDocs into offline HTML. This repo
|
||||
holds only the code that shows that HTML and the pin of which help release ships.
|
||||
|
||||
## In the app
|
||||
|
||||
- **Help > OpenNest Help** (F1 in the main window) opens the home topic;
|
||||
**Help > Your First Nest** opens the tutorial.
|
||||
- A dialog opts in to context help with `HelpTopics.Attach(this, HelpTopics.<Topic>)`;
|
||||
F1 anywhere in it opens that topic. Auto Nest and the CAD Converter do this today.
|
||||
- `HelpForm` is a single WebView2 window on its own STA thread, so modal dialogs never
|
||||
disable it. It serves the help folder from the private virtual host
|
||||
`https://help.opennest.local/`; off-site links open in the default browser. WebView2
|
||||
data goes to `%LOCALAPPDATA%\OpenNest\WebView2`.
|
||||
- The help folder is `Help\` beside `OpenNest.exe`. Set `OPENNEST_HELP_DIR` to a local
|
||||
opennest-help `site\` build to preview unreleased pages. Without either, the menu
|
||||
explains that help is not installed.
|
||||
|
||||
## Topic IDs
|
||||
|
||||
`HelpTopics` constants are IDs, not file names. The help's `topics.json` maps each ID
|
||||
to a page or `page.html#anchor`, and its build fails if a mapped page is missing. An
|
||||
unknown ID falls back to the home page. Add the ID to `topics.json` (in the help repo)
|
||||
before using a new constant here, and never rename an existing ID.
|
||||
|
||||
## Packaging
|
||||
|
||||
`scripts/help.json` pins one opennest-help release (tag and SHA-256 of its
|
||||
`opennest-help.zip`). `scripts/Install-Help.ps1` downloads it, refuses a hash mismatch
|
||||
and expands it into the package's `Help\` folder; both `Publish-Windows.ps1` and
|
||||
`Deploy-OpenNest.ps1` call it, and `build-info.json` records the help version. Pass
|
||||
`-HelpDirectory <site folder>` to either script to package a local build instead.
|
||||
|
||||
To ship new help: tag the help repo `vX.Y.Z`, wait for its release, then update `tag`
|
||||
and `sha256` here (the release attaches `opennest-help.zip.sha256`).
|
||||
|
||||
When a UI change alters a documented screen or workflow, update or recapture the
|
||||
affected opennest-help pages (its README covers capture and markup).
|
||||
+3
-1
@@ -23,7 +23,9 @@ pulls from GitHub every hour; it refuses pushes.
|
||||
4. Download the `OpenNest-X.Y.Z-win-x64` artifact and verify its `.sha256`.
|
||||
It contains `OpenNest.vX.Y.Z.win-x64.zip`, with the .NET runtime, native
|
||||
dependencies, shipped configurations, all three post-processors, license,
|
||||
and `build-info.json` identifying the exact source commit. The nesting engines
|
||||
the `Help` folder from the opennest-help release pinned in `scripts/help.json`
|
||||
(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
|
||||
checks that every built-in engine instantiates from it, that the renamed
|
||||
|
||||
@@ -22,7 +22,9 @@ Param(
|
||||
[string]$FeedHost,
|
||||
[string]$FeedUser = "aj",
|
||||
[Parameter(Mandatory)]
|
||||
[string]$RemoteDataDir
|
||||
[string]$RemoteDataDir,
|
||||
# A built opennest-help site/ folder to package instead of the release pinned in help.json.
|
||||
[string]$HelpDirectory
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -55,6 +57,9 @@ Write-Host "Publishing self-contained build..."
|
||||
& dotnet publish $ProjectPath -c $PublishConfiguration -r win-x64 --self-contained true -o $PublishDir
|
||||
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" }
|
||||
|
||||
$helpVersion = & (Join-Path $ScriptDir 'Install-Help.ps1') -Destination (Join-Path $PublishDir 'Help') -HelpDirectory $HelpDirectory
|
||||
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
|
||||
if ($LASTEXITCODE -ne 0) { throw "vpk pack failed with exit code $LASTEXITCODE" }
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<#
|
||||
Put the OpenNest help site into a package's Help folder.
|
||||
|
||||
By default this downloads the opennest-help release pinned in scripts/help.json and
|
||||
refuses it unless its SHA-256 matches. Pass -HelpDirectory to use a locally built
|
||||
site/ folder instead (e.g. while writing new help pages). Returns a short
|
||||
description of the help version for build-info.json.
|
||||
|
||||
Example:
|
||||
./scripts/Install-Help.ps1 -Destination artifacts/OpenNest/Help
|
||||
./scripts/Install-Help.ps1 -Destination bin/publish/Help -HelpDirectory ../opennest-help/site
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)] [string] $Destination,
|
||||
[string] $HelpDirectory
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
if (Test-Path $Destination) { throw "Refusing to overwrite existing help folder: $Destination" }
|
||||
|
||||
if ($HelpDirectory) {
|
||||
if (-not (Test-Path (Join-Path $HelpDirectory 'index.html'))) { throw "No help site in $HelpDirectory (index.html missing)." }
|
||||
New-Item -ItemType Directory -Path $Destination | Out-Null
|
||||
Copy-Item (Join-Path $HelpDirectory '*') $Destination -Recurse
|
||||
return "local:$([IO.Path]::GetFullPath($HelpDirectory))"
|
||||
}
|
||||
|
||||
$pin = Get-Content (Join-Path $PSScriptRoot 'help.json') -Raw | ConvertFrom-Json
|
||||
$version = "$($pin.repository)@$($pin.tag)"
|
||||
$zip = Join-Path ([IO.Path]::GetTempPath()) "opennest-help-$([guid]::NewGuid().ToString('N')).zip"
|
||||
try {
|
||||
Invoke-WebRequest "https://github.com/$($pin.repository)/releases/download/$($pin.tag)/opennest-help.zip" -OutFile $zip -UseBasicParsing
|
||||
$hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($hash -ne $pin.sha256) { throw "Help $version hash $hash does not match scripts/help.json." }
|
||||
Expand-Archive -Path $zip -DestinationPath $Destination
|
||||
}
|
||||
finally { Remove-Item $zip -ErrorAction SilentlyContinue }
|
||||
return $version
|
||||
@@ -3,7 +3,9 @@ param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$')]
|
||||
[string] $Version,
|
||||
[string] $OutputDirectory = 'artifacts'
|
||||
[string] $OutputDirectory = 'artifacts',
|
||||
# A built opennest-help site/ folder to package instead of the release pinned in help.json.
|
||||
[string] $HelpDirectory
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -39,6 +41,9 @@ try {
|
||||
if ($name -eq 'GravographIS') { Copy-Item "$staging/System.IO.Ports.dll" $package }
|
||||
}
|
||||
|
||||
# Help ships as the opennest-help release pinned in help.json, verified against its hash.
|
||||
$helpVersion = & (Join-Path $PSScriptRoot 'Install-Help.ps1') -Destination (Join-Path $package 'Help') -HelpDirectory $HelpDirectory
|
||||
|
||||
New-Item -ItemType Directory -Path (Join-Path $package 'Schemes') -Force | Out-Null
|
||||
Copy-Item LICENSE $package
|
||||
@{
|
||||
@@ -46,6 +51,7 @@ try {
|
||||
sourceCommit = $commit
|
||||
runtime = 'win-x64'
|
||||
selfContained = $true
|
||||
help = $helpVersion
|
||||
} | ConvertTo-Json | Set-Content (Join-Path $package 'build-info.json') -Encoding utf8NoBOM
|
||||
|
||||
$required = @(
|
||||
@@ -54,7 +60,8 @@ try {
|
||||
'onnxruntime.dll', 'System.IO.Ports.dll', 'Configurations/PipeFlangeShape.json',
|
||||
'Posts/OpenNest.Posts.Cincinnati.dll', 'Posts/OpenNest.Posts.Cincinnati.json',
|
||||
'Posts/OpenNest.Posts.CincinnatiCIFiber.dll', 'Posts/OpenNest.Posts.CincinnatiCIFiber.json',
|
||||
'Posts/OpenNest.Posts.GravographIS.dll', 'LICENSE', 'build-info.json'
|
||||
'Posts/OpenNest.Posts.GravographIS.dll', 'Help/index.html', 'Help/topics.json',
|
||||
'LICENSE', 'build-info.json'
|
||||
)
|
||||
foreach ($file in $required) {
|
||||
if (-not (Test-Path (Join-Path $package $file) -PathType Leaf)) { throw "Package missing $file" }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"repository": "ajisaacs/opennest-help",
|
||||
"tag": "v0.1.0",
|
||||
"sha256": ""
|
||||
}
|
||||
Reference in new issue
Block a user