Skip to content

Commit 83332c7

Browse files
committed
Release v1.1.0: Add Lifter.Avalonia package and complete documentation site
- Add Lifter.Avalonia package with HostedApplication support - Create comprehensive GitHub Pages documentation site with Jekyll - Add documentation for all packages (Core, MAUI, Avalonia, Blazor) - Add advanced topics documentation (WatchDog service) - Update GitHub Actions workflow for NuGet and docs publishing - Bump all packages to version 1.1.0 - Update README with Lifter.Avalonia and NuGet badges - Update CHANGELOG for v1.1.0 release
1 parent 2294cfd commit 83332c7

File tree

18 files changed

+1704
-8
lines changed

18 files changed

+1704
-8
lines changed

.github/workflows/publish.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# This workflow builds, tests, publishes NuGet packages, and updates documentation
2+
name: Publish to NuGet and Update Docs
3+
4+
on:
5+
push:
6+
tags:
7+
- 'v*.*.*' # Triggers on version tags like v1.1.0
8+
workflow_dispatch:
9+
inputs:
10+
version:
11+
description: 'Version to publish (e.g., 1.1.0)'
12+
required: true
13+
14+
jobs:
15+
build-test-publish:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: write # Required for creating GitHub Release and pushing to gh-pages
19+
20+
steps:
21+
# 1. Checkout code
22+
- name: Checkout code
23+
uses: actions/checkout@v4
24+
25+
# 2. Setup .NET SDK (both 8 and 9 for multi-targeting)
26+
- name: Setup .NET
27+
uses: actions/setup-dotnet@v4
28+
with:
29+
dotnet-version: |
30+
8.0.x
31+
9.0.x
32+
33+
# 3. Extract version from tag or manual input
34+
- name: Extract version
35+
id: version
36+
run: |
37+
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
38+
echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
39+
else
40+
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
41+
fi
42+
43+
# 4. Restore dependencies
44+
- name: Restore dependencies
45+
run: dotnet restore Lifter.Libraries.sln
46+
47+
# 5. Build in Release configuration
48+
- name: Build
49+
run: dotnet build Lifter.Libraries.sln --no-restore --configuration Release
50+
51+
# 6. Run tests
52+
- name: Run tests
53+
run: dotnet test Lifter.Libraries.sln --no-build --configuration Release --verbosity normal
54+
55+
# --- Publishing (only on tag push or manual workflow) ---
56+
57+
# 7. Pack NuGet packages
58+
- name: Pack NuGet packages
59+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
60+
run: dotnet pack Lifter.Libraries.sln --no-build --configuration Release --output ./packages
61+
62+
# 8. Publish to NuGet
63+
- name: Publish to NuGet
64+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
65+
run: dotnet nuget push "./packages/*.nupkg" --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate
66+
67+
# 9. Create versioned documentation
68+
- name: Create versioned documentation
69+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
70+
run: |
71+
VERSION=${{ steps.version.outputs.VERSION }}
72+
MAJOR_MINOR=$(echo $VERSION | cut -d. -f1,2)
73+
74+
# Create versioned docs directory
75+
mkdir -p docs/v${MAJOR_MINOR}
76+
77+
# Copy latest docs to versioned directory
78+
cp -r docs/latest/* docs/v${MAJOR_MINOR}/
79+
80+
# Update version placeholders
81+
find docs/v${MAJOR_MINOR} -type f -name "*.md" -exec sed -i "s/VERSION_PLACEHOLDER/${VERSION}/g" {} \;
82+
find docs/v${MAJOR_MINOR} -type f -name "*.md" -exec sed -i "s|/latest/|/v${MAJOR_MINOR}/|g" {} \;
83+
84+
# 10. Deploy documentation to GitHub Pages
85+
- name: Deploy to GitHub Pages
86+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
87+
uses: JamesIves/github-pages-deploy-action@v4
88+
with:
89+
branch: gh-pages
90+
folder: docs
91+
clean: false
92+
commit-message: "docs: update documentation for v${{ steps.version.outputs.VERSION }}"
93+
94+
# 11. Create GitHub Release
95+
- name: Create GitHub Release
96+
if: startsWith(github.ref, 'refs/tags/v')
97+
uses: softprops/action-gh-release@v2
98+
with:
99+
files: ./packages/*.nupkg
100+
generate_release_notes: true

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to this project will be documented in this file. See [versionize](https://github.com/versionize/versionize) for commit guidelines.
44

5+
<a name="1.1.0"></a>
6+
## [1.1.0](https://www.github.com/lucafabbri/Lifter/releases/tag/v1.1.0) (2026-01-15)
7+
8+
### Features
9+
10+
* **Lifter.Avalonia** - New package for Avalonia UI applications with HostedApplication base class
11+
* **Documentation Site** - GitHub Pages documentation with versioning at https://lucafabbri.github.io/Lifter/
12+
* **GitHub Actions** - Updated publish workflow with automated docs deployment
13+
14+
### Changes
15+
16+
* Updated all packages to version 1.1.0
17+
* Enhanced README with Lifter.Avalonia examples and NuGet badges
18+
19+
---
20+
21+
522
<a name="1.0.2"></a>
623
## [1.0.2](https://www.github.com/lucafabbri/Lifter/releases/tag/v1.0.2) (2025-09-09)
724

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Avalonia.Controls.ApplicationLifetimes;
4+
using Avalonia.Threading;
5+
using Lifter.Core;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Hosting;
9+
10+
namespace Lifter.Avalonia;
11+
12+
/// <summary>
13+
/// An abstract base class for creating a hosted Avalonia application with
14+
/// dependency injection, configuration, and IHostedService support.
15+
/// </summary>
16+
/// <typeparam name="TMainView">
17+
/// The main view type. Must be a Control and implement <see cref="IHostedView"/>.
18+
/// </typeparam>
19+
public abstract class HostedApplication<TMainView> : global::Avalonia.Application
20+
where TMainView : Control, IHostedView
21+
{
22+
/// <summary>
23+
/// The host application builder used to configure services and configuration.
24+
/// </summary>
25+
protected readonly HostApplicationBuilder _hostApplicationBuilder;
26+
27+
/// <summary>
28+
/// Gets the application's configured services.
29+
/// </summary>
30+
/// <exception cref="InvalidOperationException">Thrown if the application host has not been built yet.</exception>
31+
public IServiceProvider Services => Host?.Services
32+
?? throw new InvalidOperationException("The application host has not been built yet.");
33+
34+
/// <summary>
35+
/// Gets the application's configuration.
36+
/// </summary>
37+
public IConfiguration Configuration => _hostApplicationBuilder.Configuration;
38+
39+
/// <summary>
40+
/// Gets the configured application host.
41+
/// </summary>
42+
public IHost? Host { get; private set; }
43+
44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="HostedApplication{TMainView}"/> class.
46+
/// </summary>
47+
protected HostedApplication()
48+
{
49+
_hostApplicationBuilder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder();
50+
}
51+
52+
/// <inheritdoc/>
53+
public sealed override void OnFrameworkInitializationCompleted()
54+
{
55+
// Show splash screen if provided (optional)
56+
Window? splashWindow = null;
57+
var splashScreenContent = CreateSplashScreen();
58+
59+
if (splashScreenContent != null)
60+
{
61+
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
62+
{
63+
splashWindow = new Window
64+
{
65+
Content = splashScreenContent,
66+
WindowStartupLocation = WindowStartupLocation.CenterScreen,
67+
SystemDecorations = SystemDecorations.None,
68+
SizeToContent = SizeToContent.WidthAndHeight,
69+
Title = "Loading..."
70+
};
71+
desktop.MainWindow = splashWindow;
72+
}
73+
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
74+
{
75+
singleView.MainView = splashScreenContent;
76+
}
77+
}
78+
79+
// Build the host
80+
Host = BuildHostInternal();
81+
82+
// Start initialization on background thread
83+
_ = Task.Run(async () =>
84+
{
85+
try
86+
{
87+
// Allow derived classes to perform custom initialization
88+
await OnHostInitializedAsync();
89+
90+
// Switch to UI thread for view setup
91+
await Dispatcher.UIThread.InvokeAsync(() =>
92+
{
93+
SetupMainView(splashWindow);
94+
});
95+
96+
// Start hosted services
97+
HostManager.Initialize(Services);
98+
await HostManager.StartAsync();
99+
}
100+
catch (Exception ex)
101+
{
102+
// Log or handle startup errors
103+
Console.WriteLine($"Application startup failed: {ex}");
104+
throw;
105+
}
106+
});
107+
108+
base.OnFrameworkInitializationCompleted();
109+
}
110+
111+
/// <summary>
112+
/// Builds and configures the application host.
113+
/// </summary>
114+
private IHost BuildHostInternal()
115+
{
116+
// Register the main view as a singleton
117+
_hostApplicationBuilder.Services.AddSingleton<TMainView>();
118+
119+
// Allow derived classes to register services
120+
ConfigureServices(_hostApplicationBuilder.Services, _hostApplicationBuilder.Configuration);
121+
122+
return _hostApplicationBuilder.Build();
123+
}
124+
125+
/// <summary>
126+
/// Sets up the main view for Desktop or Mobile platforms.
127+
/// </summary>
128+
/// <param name="splashWindow">The splash window to close after setup (Desktop only).</param>
129+
private void SetupMainView(Window? splashWindow)
130+
{
131+
var mainView = Services.GetRequiredService<TMainView>();
132+
133+
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
134+
{
135+
// Desktop: Create a Window with the view as content
136+
var windowConfig = Services.GetService<WindowConfiguration>() ?? new WindowConfiguration();
137+
138+
var mainWindow = new Window
139+
{
140+
Title = windowConfig.Title,
141+
Content = mainView,
142+
Width = windowConfig.Width,
143+
Height = windowConfig.Height,
144+
WindowState = windowConfig.WindowState,
145+
WindowStartupLocation = windowConfig.StartupLocation
146+
};
147+
148+
#if DEBUG
149+
// Enable DevTools in debug mode
150+
mainWindow.AttachDevTools();
151+
#endif
152+
153+
desktop.MainWindow = mainWindow;
154+
mainWindow.Show();
155+
156+
// Close splash screen
157+
splashWindow?.Close();
158+
}
159+
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
160+
{
161+
// Mobile: Set the view directly
162+
singleView.MainView = mainView;
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Creates the splash screen control to be displayed during initialization.
168+
/// </summary>
169+
/// <returns>The splash screen control, or null if no splash screen should be shown.</returns>
170+
/// <remarks>
171+
/// Override this method in derived classes to provide a custom splash screen.
172+
/// The splash screen will be shown while the application is initializing.
173+
/// </remarks>
174+
protected virtual Control? CreateSplashScreen()
175+
{
176+
return null; // No splash screen by default
177+
}
178+
179+
/// <summary>
180+
/// Called when the host has completed its initialization process asynchronously.
181+
/// </summary>
182+
/// <returns>A task representing the asynchronous operation.</returns>
183+
/// <remarks>
184+
/// Override this method to perform custom initialization tasks after the host is built
185+
/// but before the main view is displayed. This runs on a background thread.
186+
/// </remarks>
187+
protected virtual Task OnHostInitializedAsync()
188+
{
189+
return Task.CompletedTask;
190+
}
191+
192+
/// <summary>
193+
/// Configures application services and registers them with the dependency injection container.
194+
/// </summary>
195+
/// <param name="services">The <see cref="IServiceCollection"/> to which services are added.</param>
196+
/// <param name="configuration">The <see cref="IConfiguration"/> instance containing application configuration settings.</param>
197+
/// <remarks>
198+
/// This method is called during application startup to configure services.
199+
/// Use this method to register your application's services, background services, and dependencies.
200+
/// </remarks>
201+
protected abstract void ConfigureServices(IServiceCollection services, IConfiguration configuration);
202+
}

Lifter.Avalonia/IHostedView.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Lifter.Avalonia;
2+
3+
/// <summary>
4+
/// Marker interface for views that can be hosted in a <see cref="HostedApplication{TMainView}"/>.
5+
/// </summary>
6+
/// <remarks>
7+
/// This interface is used as a compile-time constraint to ensure that only appropriate
8+
/// views are used with the HostedApplication framework.
9+
/// </remarks>
10+
public interface IHostedView
11+
{
12+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<!-- Multi-targeting for .NET 8 and .NET 9 -->
5+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
9+
10+
<!-- NuGet package metadata -->
11+
<PackageId>Lifter.Avalonia</PackageId>
12+
<Version>1.1.0</Version>
13+
<Authors>Luca Fabbri</Authors>
14+
<PackageContact>lucafabbri84@live.it</PackageContact>
15+
<Copyright>Copyright (c) Luca Fabbri 2024</Copyright>
16+
<Description>
17+
Avalonia UI integration for Lifter. This package provides a HostedApplication base class
18+
with dependency injection, configuration, and IHostedService support for Avalonia applications.
19+
</Description>
20+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
21+
<PackageProjectUrl>https://github.com/lucafabbri/Lifter</PackageProjectUrl>
22+
<RepositoryUrl>https://github.com/lucafabbri/Lifter</RepositoryUrl>
23+
<RepositoryType>git</RepositoryType>
24+
<PackageTags>avalonia;hostedservices;hosting;di;configuration;lifter</PackageTags>
25+
<PackageReleaseNotes>Initial release.</PackageReleaseNotes>
26+
</PropertyGroup>
27+
28+
<PropertyGroup Condition="'$(Configuration)'=='Release'">
29+
<!-- SourceLink configuration -->
30+
<PublishRepositoryUrl>true</PublishRepositoryUrl>
31+
<IncludeSymbols>true</IncludeSymbols>
32+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
33+
</PropertyGroup>
34+
35+
<ItemGroup>
36+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
37+
</ItemGroup>
38+
39+
<ItemGroup>
40+
<ProjectReference Include="..\Lifter.Core\Lifter.Core.csproj" />
41+
</ItemGroup>
42+
43+
<ItemGroup>
44+
<PackageReference Include="Avalonia" Version="11.3.11" />
45+
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11" Condition="'$(Configuration)' == 'Debug'" />
46+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.9" />
47+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.9" />
48+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.9" />
49+
</ItemGroup>
50+
51+
</Project>

0 commit comments

Comments
 (0)