Merge pull request #1093 from ElectronNET/develop

Release 0.5.2
This commit is contained in:
Florian Rappl
2026-08-03 14:53:48 +02:00
committed by GitHub
36 changed files with 939 additions and 169 deletions

View File

@@ -1,3 +1,16 @@
# 0.5.2
## ElectronNET.Core
- Fixed token param being appended to external URLs (#1075)
- Fixed startup mode discriminator in case of existing `wwwroot` (#1050)
- Fixed CA1416 on ASP.NET project (#1091) @epsnm
- Fixed loading issue in plain Visual Studio (#1084) @epsnm
- Fixed bloated ASAR file (#1080) @epsnm
- Improved selection of runtime identifier (#1081) @epsnm
- Added more variants for `UseElectron` (#1076) @AeonSake
- Added support for modern MacOS app icon (#1047)
# 0.5.1
## ElectronNET.Core

8
Directory.Packages.props Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file prevents unintended imports of unrelated MSBuild files -->
<!-- Uncomment to include parent Directory.Packages.props file -->
<!--<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />-->
</Project>

View File

@@ -54,7 +54,7 @@ Add the Electron.NET configuration to your `.csproj` file:
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.5.0" />
<PackageReference Include="ElectronNET.Core" Version="0.5.2" />
</ItemGroup>
```

View File

@@ -70,6 +70,12 @@ Since electron builder still expects a `package.json` file to exist, ElectronNET
}
```
### App Icon Path
The `ElectronIcon` property supports classic icon files (such as `.ico` and `.icns`) and modern macOS `.icon` app icon packages.
For `.icon`, provide the folder path (for example `Assets/MyApp.icon`). During build/publish, Electron.NET copies the full directory into the Electron output so `electron-builder.json` can reference it (for example `"mac": { "icon": "MyApp.icon" }`).
### Node.js Integration
Electron.NET requires Node.js integration to be enabled for IPC to function. If you are not using the IPC functionality you can disable Node.js integration like so:

View File

@@ -19,6 +19,7 @@ namespace ElectronNET.API
internal WindowManager()
{
EnsureBrowserWindowClosedSubscription();
}
internal static WindowManager Instance
@@ -106,17 +107,6 @@ namespace ElectronNET.API
tcs.SetResult(browserWindow);
});
BridgeConnector.Socket.Once<int[]>("BrowserWindowClosed", (ids) =>
{
for (int index = 0; index < _browserWindows.Count; index++)
{
if (!ids.Contains(_browserWindows[index].Id))
{
_browserWindows.RemoveAt(index);
}
}
});
if (loadUrl.Equals("http://localhost", StringComparison.OrdinalIgnoreCase) && ElectronNetRuntime.AspNetWebPort.HasValue)
{
loadUrl = $"{loadUrl}:{ElectronNetRuntime.AspNetWebPort}";
@@ -149,6 +139,40 @@ namespace ElectronNET.API
return await tcs.Task.ConfigureAwait(false);
}
private readonly object _browserWindowSubscriptionSync = new();
private bool _browserWindowClosedSubscribed;
private void EnsureBrowserWindowClosedSubscription()
{
if (_browserWindowClosedSubscribed)
{
return;
}
lock (_browserWindowSubscriptionSync)
{
if (_browserWindowClosedSubscribed)
{
return;
}
BridgeConnector.Socket.On<int[]>("BrowserWindowClosed", HandleBrowserWindowClosed);
_browserWindowClosedSubscribed = true;
}
}
private void HandleBrowserWindowClosed(int[] ids)
{
if (ids == null || ids.Length == 0)
{
_browserWindows.Clear();
return;
}
var existingIds = ids.ToHashSet();
_browserWindows.RemoveAll(window => !existingIds.Contains(window.Id));
}
private bool IsWindows10()
{
return RuntimeInformation.OSDescription.Contains("Windows 10");

View File

@@ -50,8 +50,6 @@
internal static int? ElectronProcessId { get; set; }
internal static Func<Task> OnAppReadyCallback { get; set; }
internal static ISocketConnection GetSocket()
{
return RuntimeControllerCore?.Socket;

View File

@@ -44,5 +44,98 @@
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<string[], Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (serviceProvider) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<IServiceProvider, Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="WebApplicationBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process, forwarded to Electron.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="WebApplicationBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// var builder = WebApplication.CreateBuilder(args)
/// .UseElectron(args, async (serviceProvider, processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
///
/// var app = builder.Build();
/// app.MapRazorPages();
/// app.Run();
/// </code>
/// </example>
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func<IServiceProvider, string[], Task> onAppReadyCallback)
{
builder.WebHost.UseElectron(args, onAppReadyCallback);
return builder;
}
}
}

View File

@@ -61,8 +61,154 @@
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<Task> onAppReadyCallback)
{
ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(_ => new AppReadyCallbackResolver(onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<string[], Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(_ => new AppReadyCallbackResolver(args, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (serviceProvider) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<IServiceProvider, Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(provider => new AppReadyCallbackResolver(provider, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
/// <summary>
/// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback.
/// </summary>
/// <param name="builder">The <see cref="IWebHostBuilder"/> to extend.</param>
/// <param name="args">The command-line arguments passed to the process.</param>
/// <param name="onAppReadyCallback">
/// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization.
/// </param>
/// <returns>
/// The same <see cref="IWebHostBuilder"/> instance to enable fluent configuration.
/// </returns>
/// <example>
/// <code language="csharp">
/// using Microsoft.AspNetCore.Hosting;
/// using Microsoft.Extensions.Hosting;
/// using ElectronNET.API;
///
/// public class Program
/// {
/// public static void Main(string[] args)
/// {
/// Host.CreateDefaultBuilder(args)
/// .ConfigureWebHostDefaults(webBuilder =>
/// {
/// webBuilder.UseStartup&lt;Startup&gt;();
/// webBuilder.UseElectron(args, async (serviceProvider, processArgs) =>
/// {
/// // Create the main browser window or perform other startup tasks.
/// });
/// })
/// .Build()
/// .Run();
/// }
/// }
/// </code>
/// </example>
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func<IServiceProvider, string[], Task> onAppReadyCallback)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<IAppReadyCallbackResolver>(provider => new AppReadyCallbackResolver(provider, args, onAppReadyCallback));
});
return UseElectronCore(builder, args);
}
private static IWebHostBuilder UseElectronCore(IWebHostBuilder builder, string[] args)
{
// no matter how this is set - let's unset to prevent Electron not starting as expected
// e.g., VS Code sets this env variable, but this will cause `require("electron")` to not
// work as expected, see issue #952
@@ -70,14 +216,16 @@
var webPort = ElectronNetRuntime.AspNetWebPort ?? 0;
// check for the content folder if its exists in base director otherwise no need to include
// It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
// now we have implemented the live reload if app is run using /watch then we need to use the default project path.
// In packaged mode, static content is deployed alongside the app binaries, so we must
// point content root to the process base directory. In unpackaged/watch scenarios we
// keep the default project content root to preserve live reload behavior.
var isPackagedStartup = ElectronNetRuntime.StartupMethod == StartupMethod.PackagedElectronFirst ||
ElectronNetRuntime.StartupMethod == StartupMethod.PackagedDotnetFirst;
// For port 0 (dynamic port assignment), Kestrel requires binding to specific IP (127.0.0.1) not localhost
var host = webPort == 0 ? "127.0.0.1" : "localhost";
if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
if (isPackagedStartup)
{
builder = builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
.UseUrls($"http://{host}:{webPort}");

View File

@@ -13,23 +13,6 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Nullable>disable</Nullable>
<RootNamespace>ElectronNET</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net10.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net10.0|AnyCPU'">
<NoWarn>1701;1702;4014;CS4014;CA1416;CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -17,13 +17,15 @@
{
private readonly IServer server;
private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter;
private readonly IAppReadyCallbackResolver callbackResolver;
private readonly IElectronAuthenticationService authenticationService;
private SocketBridgeService socketBridge;
protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null)
protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null)
{
this.server = server;
this.aspNetLifetimeAdapter = aspNetLifetimeAdapter;
this.callbackResolver = callbackResolver;
this.authenticationService = authenticationService;
this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready;
this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping;
@@ -130,20 +132,20 @@
private async Task RunReadyCallback()
{
if (ElectronNetRuntime.OnAppReadyCallback == null)
if (!callbackResolver.HasCallback)
{
Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup.");
Console.WriteLine("Warning: No OnReadyCallback provided in UseElectron() setup.");
return;
}
try
{
await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false);
await callbackResolver.Invoke().ConfigureAwait(false);
}
catch (Exception ex)
{
Console.WriteLine("Exception while executing OnAppReadyCallback. Stopping...\n" + ex);
this.Stop();
_ = this.Stop();
}
}
}

View File

@@ -14,7 +14,7 @@
{
private ElectronProcessBase electronProcess;
public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService)
public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService)
{
}

View File

@@ -11,7 +11,7 @@
{
private ElectronProcessBase electronProcess;
public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService)
public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService)
{
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Threading.Tasks;
namespace ElectronNET.AspNet.Runtime
{
internal class AppReadyCallbackResolver : IAppReadyCallbackResolver
{
private readonly Func<Task> _callback;
public AppReadyCallbackResolver()
{ }
public AppReadyCallbackResolver(Func<Task> callback)
{
_callback = callback;
}
public AppReadyCallbackResolver(string[] args, Func<string[], Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(args);
}
}
public AppReadyCallbackResolver(IServiceProvider serviceProvider, Func<IServiceProvider, Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(serviceProvider);
}
}
public AppReadyCallbackResolver(IServiceProvider serviceProvider, string[] args, Func<IServiceProvider, string[], Task> callback)
{
if (callback != null)
{
_callback = () => callback.Invoke(serviceProvider, args);
}
}
public bool HasCallback => _callback != null;
public Task Invoke() => _callback?.Invoke() ?? Task.CompletedTask;
}
}

View File

@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace ElectronNET.AspNet.Runtime
{
internal interface IAppReadyCallbackResolver
{
public bool HasCallback { get; }
public Task Invoke();
}
}

View File

@@ -70,7 +70,7 @@
<ProjectReference Include="..\ElectronNET.API\ElectronNET.API.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.5.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
</ItemGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.targets" Condition="$(ElectronNetDevMode)" />

View File

@@ -230,7 +230,10 @@ module.exports = (socket, app) => {
window = app["mainWindow"];
if (window) {
window.reload();
windows.push(window);
synchronizeWindowRegistry();
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
windows.push(window);
}
electronSocket.emit("BrowserWindowCreated", window.id);
return;
}
@@ -253,21 +256,9 @@ module.exports = (socket, app) => {
}
});
lastOptions = options;
window.on("closed", (sender) => {
for (let index = 0; index < windows.length; index++) {
const windowItem = windows[index];
try {
windowItem.id;
}
catch (error) {
if (error.message === "Object has been destroyed") {
windows.splice(index, 1);
const ids = [];
windows.forEach((x) => ids.push(x.id));
electronSocket.emit("BrowserWindowClosed", ids);
}
}
}
window.on("closed", () => {
synchronizeWindowRegistry();
emitBrowserWindowClosed();
});
app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the
@@ -280,8 +271,20 @@ module.exports = (socket, app) => {
// Append authentication token to initial URL if available
const token = global["authToken"];
if (token) {
const separator = loadUrl.includes("?") ? "&" : "?";
window.loadURL(`${loadUrl}${separator}token=${token}`);
try {
const url = new URL(loadUrl);
const isLocal = url.hostname === "localhost" ||
url.hostname === "127.0.0.1" ||
url.hostname === "::1";
if (isLocal) {
url.searchParams.set("token", token);
}
window.loadURL(url.toString());
}
catch {
// Handle invalid URLs or file:// URLs if needed
window.loadURL(loadUrl);
}
}
else {
window.loadURL(loadUrl);
@@ -691,12 +694,47 @@ module.exports = (socket, app) => {
getWindowById(id).setBrowserView((0, browserView_1.browserViewMediateService)(browserViewId));
});
function getWindowById(id) {
const runtimeWindow = electron_1.BrowserWindow.fromId(id);
if (runtimeWindow) {
return runtimeWindow;
}
synchronizeWindowRegistry();
for (let index = 0; index < windows.length; index++) {
const element = windows[index];
if (element.id === id) {
if (tryGetWindowId(element) === id) {
return element;
}
}
throw new Error(`BrowserWindow with id '${id}' was not found.`);
}
function tryGetWindowId(element) {
try {
return element.id;
}
catch {
return null;
}
}
function synchronizeWindowRegistry() {
const runtimeWindows = electron_1.BrowserWindow.getAllWindows();
const runtimeWindowIds = new Set(runtimeWindows.map((entry) => entry.id));
for (let index = windows.length - 1; index >= 0; index--) {
const windowId = tryGetWindowId(windows[index]);
if (windowId === null || !runtimeWindowIds.has(windowId)) {
windows.splice(index, 1);
}
}
readyToShowWindowsIds = readyToShowWindowsIds.filter((entryId) => runtimeWindowIds.has(entryId));
}
function emitBrowserWindowClosed() {
const ids = [];
for (const entry of windows) {
const windowId = tryGetWindowId(entry);
if (windowId !== null) {
ids.push(windowId);
}
}
electronSocket.emit("BrowserWindowClosed", ids);
}
};
//# sourceMappingURL=browserWindows.js.map

File diff suppressed because one or more lines are too long

View File

@@ -255,7 +255,10 @@ export = (socket: Socket, app: Electron.App) => {
window = app["mainWindow"];
if (window) {
window.reload();
windows.push(window);
synchronizeWindowRegistry();
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
windows.push(window);
}
electronSocket.emit("BrowserWindowCreated", window.id);
return;
}
@@ -283,21 +286,9 @@ export = (socket: Socket, app: Electron.App) => {
lastOptions = options;
window.on("closed", (sender) => {
for (let index = 0; index < windows.length; index++) {
const windowItem = windows[index];
try {
windowItem.id;
} catch (error) {
if (error.message === "Object has been destroyed") {
windows.splice(index, 1);
const ids = [];
windows.forEach((x) => ids.push(x.id));
electronSocket.emit("BrowserWindowClosed", ids);
}
}
}
window.on("closed", () => {
synchronizeWindowRegistry();
emitBrowserWindowClosed();
});
app.on("activate", () => {
@@ -313,8 +304,23 @@ export = (socket: Socket, app: Electron.App) => {
const token = global["authToken"];
if (token) {
const separator = loadUrl.includes("?") ? "&" : "?";
window.loadURL(`${loadUrl}${separator}token=${token}`);
try {
const url = new URL(loadUrl);
const isLocal =
url.hostname === "localhost" ||
url.hostname === "127.0.0.1" ||
url.hostname === "::1";
if (isLocal) {
url.searchParams.set("token", token);
}
window.loadURL(url.toString());
} catch {
// Handle invalid URLs or file:// URLs if needed
window.loadURL(loadUrl);
}
} else {
window.loadURL(loadUrl);
}
@@ -892,11 +898,57 @@ export = (socket: Socket, app: Electron.App) => {
});
function getWindowById(id: number): Electron.BrowserWindow {
const runtimeWindow = BrowserWindow.fromId(id);
if (runtimeWindow) {
return runtimeWindow;
}
synchronizeWindowRegistry();
for (let index = 0; index < windows.length; index++) {
const element = windows[index];
if (element.id === id) {
if (tryGetWindowId(element) === id) {
return element;
}
}
throw new Error(`BrowserWindow with id '${id}' was not found.`);
}
function tryGetWindowId(element: Electron.BrowserWindow): number | null {
try {
return element.id;
} catch {
return null;
}
}
function synchronizeWindowRegistry(): void {
const runtimeWindows = BrowserWindow.getAllWindows();
const runtimeWindowIds = new Set(runtimeWindows.map((entry) => entry.id));
for (let index = windows.length - 1; index >= 0; index--) {
const windowId = tryGetWindowId(windows[index]);
if (windowId === null || !runtimeWindowIds.has(windowId)) {
windows.splice(index, 1);
}
}
readyToShowWindowsIds = readyToShowWindowsIds.filter((entryId) =>
runtimeWindowIds.has(entryId),
);
}
function emitBrowserWindowClosed(): void {
const ids: number[] = [];
for (const entry of windows) {
const windowId = tryGetWindowId(entry);
if (windowId !== null) {
ids.push(windowId);
}
}
electronSocket.emit("BrowserWindowClosed", ids);
}
};

View File

@@ -11,7 +11,7 @@
"dependencies": {
"dasherize": "^2.0.0",
"electron-host-hook": "file:./ElectronHostHook",
"electron-updater": "^6.6.2",
"electron-updater": "^6.8.9",
"image-size": "^1.2.1",
"socket.io": "^4.8.1"
},
@@ -476,9 +476,9 @@
"optional": true
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz",
"integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -497,9 +497,9 @@
}
},
"node_modules/builder-util-runtime": {
"version": "9.5.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
@@ -785,12 +785,12 @@
"link": true
},
"node_modules/electron-updater": {
"version": "6.8.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
"version": "6.8.9",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
"integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
"license": "MIT",
"dependencies": {
"builder-util-runtime": "9.5.1",
"builder-util-runtime": "9.7.0",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lazy-val": "^1.0.5",
@@ -868,9 +868,9 @@
}
},
"node_modules/engine.io": {
"version": "6.6.8",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz",
"integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==",
"version": "6.6.9",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz",
"integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
@@ -882,7 +882,7 @@
"cors": "~2.8.5",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.20.1"
"ws": "~8.21.0"
},
"engines": {
"node": ">=10.2.0"
@@ -1495,9 +1495,19 @@
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -1957,9 +1967,9 @@
}
},
"node_modules/sax": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
"integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
@@ -2042,19 +2052,19 @@
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz",
"integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==",
"version": "2.5.8",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz",
"integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==",
"license": "MIT",
"dependencies": {
"debug": "~4.4.1",
"ws": "~8.20.1"
"ws": "~8.21.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
@@ -2227,9 +2237,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View File

@@ -19,7 +19,7 @@
"dasherize": "^2.0.0",
"electron-host-hook": "file:./ElectronHostHook",
"image-size": "^1.2.1",
"electron-updater": "^6.6.2",
"electron-updater": "^6.8.9",
"socket.io": "^4.8.1"
},
"devDependencies": {

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<!-- When this is enabled, the project will be switched from nuget packages to consuming the ElectronNet orchestration directly -->
<ElectronNetDevMode>true</ElectronNetDevMode>
<IsTest>True</IsTest>
</PropertyGroup>
<Import Project="..\ElectronNET\build\ElectronNET.Core.props" Condition="$(ElectronNetDevMode)" />

View File

@@ -0,0 +1,133 @@
using System.Diagnostics;
namespace ElectronNET.IntegrationTests.Tests;
/// <summary>
/// Tests for ElectronNET.Core.targets icon copy behavior.
/// Covers GitHub issue #1047: modern macOS .icon app-icon packages are folders
/// and must be copied recursively, not treated as a single file.
/// </summary>
public class ElectronIconTargetsTests
{
private static readonly string CorePropsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.props", "ElectronNET/build/ElectronNET.Core.props");
private static readonly string CoreTargetsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.targets", "ElectronNET/build/ElectronNET.Core.targets");
[Fact]
public async Task ElectronCoreTargets_ElectronIconDirectory_ShouldBeMappedIntoElectronOutput()
{
var tempDir = CreateTempProjectDirectory();
try
{
var iconPackageDir = Path.Combine(tempDir, "Assets", "MyApp.icon");
Directory.CreateDirectory(Path.Combine(iconPackageDir, "layers"));
await File.WriteAllTextAsync(Path.Combine(iconPackageDir, "manifest.json"), "{}");
await File.WriteAllTextAsync(Path.Combine(iconPackageDir, "layers", "foreground.png"), "not-a-real-png");
await WriteMinimalCsprojAsync(tempDir);
var (exitCode, output) = await RunDotnetMsBuildAsync(tempDir, "DumpElectronIconCopyItems");
var normalizedOutput = NormalizePathSeparators(output);
exitCode.Should().Be(0,
$"MSBuild target evaluation must succeed. Full output:\n{output}");
normalizedOutput.Should().Contain(
".electron/MyApp.icon/manifest.json",
$"the icon package root file must be mapped into .electron/MyApp.icon. Full output:\n{output}");
normalizedOutput.Should().Contain(
".electron/MyApp.icon/layers/foreground.png",
$"nested files in .icon package must preserve structure in .electron output. Full output:\n{output}");
}
finally
{
Directory.Delete(tempDir, recursive: true);
}
}
private static string FindBuildFile(string relativeFromRepoRoot, string relativeFromSrc)
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
var fromRepoRoot = Path.Combine(dir.FullName, relativeFromRepoRoot);
if (File.Exists(fromRepoRoot))
{
return Path.GetFullPath(fromRepoRoot);
}
var fromSrc = Path.Combine(dir.FullName, relativeFromSrc);
if (File.Exists(fromSrc))
{
return Path.GetFullPath(fromSrc);
}
dir = dir.Parent;
}
throw new FileNotFoundException(
$"Could not locate '{relativeFromRepoRoot}' by walking up from '{AppContext.BaseDirectory}'.");
}
private static string CreateTempProjectDirectory()
{
var tempDir = Path.Combine(Path.GetTempPath(), $"electron-net-icon-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(tempDir);
Directory.CreateDirectory(Path.Combine(tempDir, "Properties"));
return tempDir;
}
private static Task WriteMinimalCsprojAsync(string tempDir)
{
var propsPathEscaped = CorePropsPath.Replace("'", "&apos;");
var targetsPathEscaped = CoreTargetsPath.Replace("'", "&apos;");
return File.WriteAllTextAsync(
Path.Combine(tempDir, "TestApp.csproj"),
$$"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<Import Project="{{propsPathEscaped}}" />
<PropertyGroup Label="ElectronNetCommon">
<ElectronIcon>Assets/MyApp.icon</ElectronIcon>
</PropertyGroup>
<Import Project="{{targetsPathEscaped}}" />
<Target Name="DumpElectronIconCopyItems"
DependsOnTargets="ElectronResolvePaths;ElectronGetCopyToOutputDirectoryItems">
<Message Importance="High"
Text="ELECTRON_COPY_ITEMS: @(_ElectronFilesToCopyWithTargetPath->'%(TargetPath)')" />
</Target>
</Project>
""");
}
private static async Task<(int ExitCode, string Output)> RunDotnetMsBuildAsync(string workingDirectory, string target)
{
var psi = new ProcessStartInfo("dotnet", $"msbuild TestApp.csproj --nologo -v:minimal /restore /t:{target}")
{
WorkingDirectory = workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
};
using var process = Process.Start(psi)!;
var stdOut = await process.StandardOutput.ReadToEndAsync();
var stdErr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return (process.ExitCode, stdOut + stdErr);
}
private static string NormalizePathSeparators(string value)
{
return value.Replace('\\', '/');
}
}

View File

@@ -0,0 +1,55 @@
namespace ElectronNET.IntegrationTests.Tests;
/// <summary>
/// Regression checks for BrowserWindow lifecycle cleanup in WindowManager.
/// Covers GitHub issue #1008.
/// </summary>
public class WindowManagerLifecycleTests
{
private static readonly string WindowManagerFilePath = FindWindowManagerFile();
[Fact]
public void WindowManager_ShouldUsePersistentBrowserWindowClosedSubscription()
{
File.Exists(WindowManagerFilePath).Should().BeTrue(
$"WindowManager source must exist at '{WindowManagerFilePath}'.");
var content = File.ReadAllText(WindowManagerFilePath);
content.Should().Contain(
"Socket.On<int[]>(\"BrowserWindowClosed\"",
"closed-window cleanup must be wired for every close event, not just the first one.");
content.Should().NotContain(
"Socket.Once<int[]>(\"BrowserWindowClosed\"",
"a one-shot subscription causes stale BrowserWindow references after subsequent closes (issue #1008).");
}
private static string FindWindowManagerFile()
{
const string RelativeFromRepoRoot = "src/ElectronNET.API/API/WindowManager.cs";
const string RelativeFromSrc = "ElectronNET.API/API/WindowManager.cs";
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
var fromRepoRoot = Path.Combine(dir.FullName, RelativeFromRepoRoot);
if (File.Exists(fromRepoRoot))
{
return Path.GetFullPath(fromRepoRoot);
}
var fromSrc = Path.Combine(dir.FullName, RelativeFromSrc);
if (File.Exists(fromSrc))
{
return Path.GetFullPath(fromSrc);
}
dir = dir.Parent;
}
throw new FileNotFoundException(
"Could not locate WindowManager.cs by walking up from " +
$"'{AppContext.BaseDirectory}'.");
}
}

View File

@@ -8,7 +8,7 @@
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["electronnet-samples-blazorsignalr.styles.css"]" />
<link rel="stylesheet" href="@Assets["electronnet-samples-authmiddleware.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />

View File

@@ -1,6 +1,6 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">ElectronNET.Samples.BlazorSignalR</a>
<a class="navbar-brand" href="">ElectronNET.Samples.AuthMiddleware</a>
</div>
</div>

View File

@@ -14,7 +14,7 @@
<TypeScriptUseNodeJS>true</TypeScriptUseNodeJS>
<TypeScriptTSConfig>ElectronHostHook/tsconfig.json</TypeScriptTSConfig>
<TypeScriptCompileOnSaveEnabled>true</TypeScriptCompileOnSaveEnabled>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptCompileBlocked>false</TypeScriptCompileBlocked>
</PropertyGroup>
<ItemGroup>
@@ -27,8 +27,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.5.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>

View File

@@ -1123,9 +1123,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz",
"integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==",
"dev": true,
"license": "ISC"
},
@@ -1337,10 +1337,20 @@
"license": "ISC"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"

View File

@@ -34,7 +34,6 @@
<ElectronSplashScreen>wwwroot\assets\img\about@2x.png</ElectronSplashScreen>
<License>MIT</License>
<ElectronSingleInstance>false</ElectronSingleInstance>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<ElectronBuilderVersion>26.0</ElectronBuilderVersion>
</PropertyGroup>
<PropertyGroup>
@@ -76,8 +75,8 @@
<ProjectReference Include="..\ElectronNET.AspNet\ElectronNET.AspNet.csproj" Condition="$(ElectronNetDevMode)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.5.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.0" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.5.2" Condition="'$(ElectronNetDevMode)' != 'true'" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.9.3" />
</ItemGroup>

View File

@@ -4,7 +4,12 @@
<PropertyGroup Label="ElectronNetCommon">
<ElectronVersion>30.4.0</ElectronVersion>
<ElectronBuilderVersion>26.0</ElectronBuilderVersion>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-x64</RuntimeIdentifier>
<!-- Allow ElectronRuntimeIdentifier to be overridden (explicitly or via RuntimeIdentifier) -->
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(RuntimeIdentifier)' != ''">$(RuntimeIdentifier)</ElectronRuntimeIdentifier>
<!-- When using the dotnet CLI (Core MSBuild), infer the current RID as a default -->
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Core'">$([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier)</ElectronRuntimeIdentifier>
<!-- Otherwise, Full MSBuild runs only on Windows, so infer a Windows RID from OSArchitecture -->
<ElectronRuntimeIdentifier Condition="'$(ElectronRuntimeIdentifier)' == '' AND '$(MSBuildRuntimeType)' == 'Full'">win-$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLower())</ElectronRuntimeIdentifier>
<ElectronSingleInstance>true</ElectronSingleInstance>
<ElectronSplashScreen></ElectronSplashScreen>
<ElectronIcon></ElectronIcon>

View File

@@ -121,7 +121,7 @@
<!-- Electron Builder uses title for the installation dir on Linux, but it should not have spaces -->
<LinuxPrefix>linux</LinuxPrefix>
<ElectronTitle>$(Title)</ElectronTitle>
<ElectronTitle Condition="'$(RuntimeIdentifier.StartsWith($(LinuxPrefix)))' == 'true'">$(Title.Replace(' ', '-'))</ElectronTitle>
<ElectronTitle Condition="'$(ElectronRuntimeIdentifier.StartsWith($(LinuxPrefix)))' == 'true'">$(Title.Replace(' ', '-'))</ElectronTitle>
</PropertyGroup>
<ItemGroup>
@@ -177,9 +177,12 @@
</PropertyGroup>
<ItemGroup>
<!-- tsconfig.json is a build-time config, not a runtime file. Excluding it also stops
Microsoft.TypeScript.MSBuild from discovering and compiling the host's tsconfig
(types:["node"]) in the consumer project when hook TypeScript compilation is enabled. -->
<ElectronSourceFiles
Include="$(ElectronSourceFilesPath)\**\*.js;$(ElectronSourceFilesPath)\**\*.json;$(ElectronSourceFilesPath)\**\*.html"
Exclude="$(ElectronSourceFilesPath)\**\build-helper.js" />
Exclude="$(ElectronSourceFilesPath)\**\build-helper.js;$(ElectronSourceFilesPath)\**\tsconfig.json" />
</ItemGroup>
<ItemGroup>
@@ -213,6 +216,35 @@
</ElectronCustomHookPackageJson>
</ItemGroup>
<!-- Committed-JS mode (TypeScriptCompileBlocked=true): the hook TypeScript is not compiled
during the build, so the committed ElectronHostHook JavaScript (and source maps) are
packaged as-is. Reclassify them from None into .electron\ElectronHostHook content
(mirroring ElectronSourceFiles) so they reach the build output and, at publish, are swept
into app\ -> app.asar. When compilation is enabled (TypeScriptCompileBlocked!=true) this
is skipped: Microsoft.TypeScript.MSBuild compiles the hook and ElectronAdjustHostHookOutput
routes the output, so reclassifying would duplicate .electron\ElectronHostHook items.
Without one of these, require('electron-host-hook') fails ('Cannot find module .../index.js'). -->
<ItemGroup Condition="'$(ElectronHasCustomHookCode)' == 'true' AND '$(TypeScriptCompileBlocked)' == 'true'">
<None Remove="ElectronHostHook\**\*.js;ElectronHostHook\**\*.js.map" />
<ElectronCustomHookJsFiles Include="ElectronHostHook\**\*.js;ElectronHostHook\**\*.js.map"
Exclude="ElectronHostHook\node_modules\**" />
<ElectronCustomHookJsFiles Update="@(ElectronCustomHookJsFiles)">
<Link>$(ElectronDirName)\ElectronHostHook\%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ElectronCustomHookJsFiles>
</ItemGroup>
<AssignTargetPath Files="@(ElectronCustomHookJsFiles)" RootFolder="$(MSBuildProjectDirectory)"
Condition="'$(ElectronHasCustomHookCode)' == 'true' AND '$(TypeScriptCompileBlocked)' == 'true'">
<Output TaskParameter="AssignedFiles" ItemName="ElectronCustomHookJsFilesWithTargetPath" />
</AssignTargetPath>
<ItemGroup Condition="'$(ElectronHasCustomHookCode)' == 'true' AND '$(TypeScriptCompileBlocked)' == 'true'">
<FilesForPackagingFromProject Include="@(ElectronCustomHookJsFilesWithTargetPath->'%(Identity)')" />
<ContentWithTargetPath Include="@(ElectronCustomHookJsFilesWithTargetPath->'%(Identity)')" />
<Content Include="@(ElectronCustomHookJsFiles->'%(Identity)')" />
</ItemGroup>
<ItemGroup>
<ElectronIntermediatePackageJson Include="$(ElectronIntermediatePackageJson)" />
</ItemGroup>
@@ -231,13 +263,45 @@
</Target>
<!-- When hook TypeScript compilation is enabled (TypeScriptCompileBlocked!=true), restore the
hook's npm dependencies into ElectronHostHook\node_modules before tsc runs so its imports
(e.g. socket.io) resolve. Incremental: only re-runs when the hook package.json changes or
node_modules is missing. Skipped in committed-JS mode and at design time. -->
<Target Name="ElectronRestoreHostHookDependencies"
BeforeTargets="CompileTypeScriptWithTSConfig;CompileTypeScript"
Condition="'@(ElectronCustomHookTsFiles->Count())' &gt; 0 AND Exists('$(MSBuildProjectDirectory)\ElectronHostHook\package.json') AND '$(TypeScriptCompileBlocked)' != 'true' AND '$(DesignTimeBuild)' != 'true'"
Inputs="$(MSBuildProjectDirectory)\ElectronHostHook\package.json"
Outputs="$(MSBuildProjectDirectory)\ElectronHostHook\node_modules\.package-lock.json">
<PropertyGroup>
<IsLinuxWsl>false</IsLinuxWsl>
<IsLinuxWsl Condition="'$(ElectronPlatform)' == 'linux' AND $([MSBuild]::IsOSPlatform('Windows'))">true</IsLinuxWsl>
<_NpmCmd>npm install</_NpmCmd>
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
</PropertyGroup>
<Message Importance="High" Text="Restoring ElectronHostHook npm dependencies for TypeScript compilation..." />
<Exec Command="$(_NpmCmd)"
WorkingDirectory="$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', 'ElectronHostHook'))"
Timeout="600000" StandardOutputImportance="Low" StandardErrorImportance="High" ContinueOnError="false" />
</Target>
<!-- Run before GetTypeScriptOutputForPublishing to adjust the target directory -->
<Target Name="ElectronAdjustHostHookOutput" BeforeTargets="GetTypeScriptOutputForPublishing" DependsOnTargets="CompileTypeScriptWithTSConfig">
<ItemGroup>
<!-- Route the compiled hook JS to .electron\ElectronHostHook. Do NOT reuse
%(DestinationRelativePath): with no outDir in the hook tsconfig, VsTsc leaves it as
an absolute path, which would produce .electron\<absolute-path>. Derive the target
from the file name instead (hook files are flat). Link is set because
GetTypeScriptOutputForPublishing re-runs AssignTargetPath, which honors Link for the
publish TargetPath. -->
<GeneratedJavascript Update="@(GeneratedJavascript)">
<DestinationRelativePath>$(ElectronDirName)\%(DestinationRelativePath)</DestinationRelativePath>
<TargetPath>$(ElectronDirName)\%(DestinationRelativePath)</TargetPath>
<DestinationRelativePath>$(ElectronDirName)\ElectronHostHook\%(Filename)%(Extension)</DestinationRelativePath>
<TargetPath>$(ElectronDirName)\ElectronHostHook\%(Filename)%(Extension)</TargetPath>
<Link>$(ElectronDirName)\ElectronHostHook\%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</GeneratedJavascript>
</ItemGroup>
@@ -249,7 +313,10 @@
<ItemGroup>
<!--<_ElectronFiles Include="$(ElectronIntermediatePackageJson)" />-->
<_ElectronFiles Include="$(ElectronSplashScreen)" Condition="'$(ElectronSplashScreen)'!=''" />
<_ElectronFiles Include="$(ElectronIcon)" Condition="'$(ElectronIcon)'!=''" />
<_ElectronFiles Include="$(ElectronIcon)"
Condition="'$(ElectronIcon)'!='' AND ( !Exists('$(ElectronIcon)') OR !$([System.IO.Directory]::Exists('$(ElectronIcon)') ) )" />
<_ElectronIconDirectoryFiles Include="$(ElectronIcon)\**\*"
Condition="'$(ElectronIcon)'!='' AND Exists('$(ElectronIcon)') AND $([System.IO.Directory]::Exists('$(ElectronIcon)') )" />
</ItemGroup>
<ItemGroup>
@@ -265,11 +332,16 @@
</ItemGroup>
<Message Importance="High" Text="_ElectronFilesToCopy: @(_ElectronFilesToCopy)" />
<Message Importance="High" Text="_ElectronIconDirectoryFiles: @(_ElectronIconDirectoryFiles)" />
<ItemGroup>
<_ElectronFilesToCopyWithTargetPath Include="@(_ElectronFilesToCopy)">
<TargetPath>$(ElectronDirName)\%(FileName)%(Extension)</TargetPath>
</_ElectronFilesToCopyWithTargetPath>
<_ElectronFilesToCopyWithTargetPath Include="@(_ElectronIconDirectoryFiles)">
<TargetPath>$(ElectronDirName)\$(ElectronIconFileName)\%(RecursiveDir)%(FileName)%(Extension)</TargetPath>
</_ElectronFilesToCopyWithTargetPath>
</ItemGroup>
<Message Text="_ElectronFilesToCopyWithTargetPath: @(_ElectronFilesToCopyWithTargetPath)" />
@@ -301,18 +373,18 @@
<Target Name="ElectronCheckVersionMismatch">
<PropertyGroup>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'win-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'win-x86'">ia32</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'win-arm64'">arm64</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'linux-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'linux-arm'">armv7l</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">arm64</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'osx-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(RuntimeIdentifier)' == 'osx-arm64'">arm64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'win-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'win-x86'">ia32</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'win-arm64'">arm64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'linux-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'linux-arm'">armv7l</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'linux-arm64'">arm64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'osx-x64'">x64</ElectronArch>
<ElectronArch Condition="'$(ElectronRuntimeIdentifier)' == 'osx-arm64'">arm64</ElectronArch>
<ElectronPlatform Condition="'$(RuntimeIdentifier)' == 'win-x64' OR '$(RuntimeIdentifier)' == 'win-x86' OR '$(RuntimeIdentifier)' == 'win-arm64'">win</ElectronPlatform>
<ElectronPlatform Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm' OR '$(RuntimeIdentifier)' == 'linux-arm64'">linux</ElectronPlatform>
<ElectronPlatform Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'">mac</ElectronPlatform>
<ElectronPlatform Condition="'$(ElectronRuntimeIdentifier)' == 'win-x64' OR '$(ElectronRuntimeIdentifier)' == 'win-x86' OR '$(ElectronRuntimeIdentifier)' == 'win-arm64'">win</ElectronPlatform>
<ElectronPlatform Condition="'$(ElectronRuntimeIdentifier)' == 'linux-x64' OR '$(ElectronRuntimeIdentifier)' == 'linux-arm' OR '$(ElectronRuntimeIdentifier)' == 'linux-arm64'">linux</ElectronPlatform>
<ElectronPlatform Condition="'$(ElectronRuntimeIdentifier)' == 'osx-x64' OR '$(ElectronRuntimeIdentifier)' == 'osx-arm64'">mac</ElectronPlatform>
<!-- npm uses different OS names than Electron -->
<NpmOs Condition="'$(ElectronPlatform)' == 'win'">win32</NpmOs>
@@ -321,7 +393,7 @@
<!-- npm CPU is same as ElectronArch except for linux-arm -->
<NpmCpu>$(ElectronArch)</NpmCpu>
<NpmCpu Condition="'$(RuntimeIdentifier)' == 'linux-arm'">arm</NpmCpu>
<NpmCpu Condition="'$(ElectronRuntimeIdentifier)' == 'linux-arm'">arm</NpmCpu>
<_CurrentOSPlatform Condition="$([MSBuild]::IsOSPlatform('Windows'))">win</_CurrentOSPlatform>
<_CurrentOSPlatform Condition="$([MSBuild]::IsOSPlatform('Linux'))">linux</_CurrentOSPlatform>
@@ -449,12 +521,16 @@
<PublishDir>$(_OriginalPublishDir)</PublishDir>
<PublishDir Condition="'$(_NonIntermediatePublishDir)' != ''">$(_NonIntermediatePublishDir)</PublishDir>
<ElectronPublishDir>$(_OriginalPublishDir)</ElectronPublishDir>
<!-- The Electron host files (main.js, package.json, runtime node_modules, ...) are packed
into app.asar. They live in a dedicated 'app' subfolder so the .NET app in 'bin' is NOT
swept into the asar by electron-builder's default 'files' glob. See directories.app below. -->
<ElectronAppDir>$(ElectronPublishDir)app\</ElectronAppDir>
</PropertyGroup>
<Error Condition="'$(_IsPlatformMismatch)' == 'true'"
Code="ELECTRON100"
Text="The target RuntimeIdentifier '$(RuntimeIdentifier)' (platform: $(ElectronPlatform)) does not match the current operating system ($(_CurrentOSPlatform)).
Text="The target RuntimeIdentifier '$(ElectronRuntimeIdentifier)' (platform: $(ElectronPlatform)) does not match the current operating system ($(_CurrentOSPlatform)).
Electron applications must be built on the target operating system:
- Windows targets (win-x64, win-x86, win-arm64) must be built on Windows
@@ -489,7 +565,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<ItemGroup>
<ElectronPublishFilesToMove Update="@(ElectronPublishFilesToMove)" >
<MoveTargetPath>$(ElectronPublishDir)%(RecursiveDir)%(FileName)%(Extension)</MoveTargetPath>
<MoveTargetPath>$(ElectronAppDir)%(RecursiveDir)%(FileName)%(Extension)</MoveTargetPath>
</ElectronPublishFilesToMove>
</ItemGroup>
@@ -503,14 +579,55 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<ElectronPublishUrlFullPath>$([System.IO.Path]::GetFullPath('$(PublishUrl.TrimEnd(&quot;\&quot;))'))</ElectronPublishUrlFullPath>
</PropertyGroup>
<PropertyGroup>
<ElectronAppDirFullPath>$([System.IO.Path]::GetFullPath('$(ElectronAppDir)'))</ElectronAppDirFullPath>
</PropertyGroup>
<PropertyGroup>
<IsLinuxWsl>false</IsLinuxWsl>
<IsLinuxWsl Condition="'$(ElectronPlatform)' == 'linux' AND $([MSBuild]::IsOSPlatform('Windows'))">true</IsLinuxWsl>
<_NpmCmd>npm install electron-builder@$(ElectronBuilderVersion) --save-dev </_NpmCmd>
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
</PropertyGroup>
<Message Importance="High" Text="ElectronPublishApp: ElectronPublishDirFullPath - $(ElectronPublishDirFullPath)" />
<Message Importance="High" Text="ElectronPublishApp: ElectronAppDirFullPath - $(ElectronAppDirFullPath)" />
<!-- Install the app's runtime (prod) dependencies into app\node_modules so they end up
inside app.asar. devDependencies (electron, eslint, typescript) are not needed here:
TypeScript is already compiled at build time and electron-builder fetches Electron itself. -->
<PropertyGroup>
<_NpmAppDepsCmd>npm install --omit=dev</_NpmAppDepsCmd>
<_NpmAppDepsCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmAppDepsCmd)'</_NpmAppDepsCmd>
</PropertyGroup>
<Exec Command="$(_NpmAppDepsCmd)" WorkingDirectory="$(ElectronAppDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
</Exec>
<Message Importance="High" Text="Electron app dependency install failed!" Condition="'$(ExecExitCode)' != '0'" />
<!-- Install electron-builder (the dev tool) into the publish root, which is the
electron-builder projectDir (cwd). app metadata + runtime deps are read from
app\ via directories.app (standard electron-builder two-package.json layout).
Render the dev package.json first: the two-package.json layout requires a
package.json at the projectDir, and electron-builder requires the 'build'
configuration to live in this development package.json (it is rejected in the
application package.json under app\). The .NET app's metadata/version come from
app\package.json instead. -->
<ItemGroup>
<DevTemplateProperty Include="ElectronPackageId" Value="$(ElectronPackageId)" />
<DevTemplateProperty Include="Title" Value="$(Title)" />
<DevTemplateProperty Include="Version" Value="$(Version)" />
</ItemGroup>
<ReplaceTemplateTask TemplateFile="$(MSBuildThisFileDirectory)package.dev.template.json"
OutputFile="$([System.IO.Path]::Combine('$(ElectronPublishDirFullPath)', 'package.json'))"
TemplateProperties="@(DevTemplateProperty)"
Condition="@(CopiedFiles->Count()) > 0" />
<PropertyGroup>
<_NpmCmd>npm install electron-builder@$(ElectronBuilderVersion)</_NpmCmd>
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'</_NpmCmd>
</PropertyGroup>
<Exec Command="$(_NpmCmd)" WorkingDirectory="$(ElectronPublishDirFullPath)" Timeout="600000" StandardOutputImportance="High" StandardErrorImportance="High" ContinueOnError="false" Condition="@(CopiedFiles->Count()) > 0">
<Output TaskParameter="ExitCode" PropertyName="ExecExitCode"/>
@@ -529,10 +646,17 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<ElectronPaParams Condition="'$(Version)' != ''">$(ElectronPaParams) -c.buildVersion &quot;$(Version)&quot;</ElectronPaParams>
<ElectronPaParams Condition="'$(Copyright)' != ''">$(ElectronPaParams) -c.copyright &quot;$(Copyright)&quot;</ElectronPaParams>
<ElectronPaParams>$(ElectronPaParams) -c.extraResources &quot;bin/**/*&quot;</ElectronPaParams>
<!-- Build app.asar from the 'app' subfolder only (host JS + runtime node_modules),
keeping the .NET app under 'bin' out of the asar. cwd stays the publish root so
extraResources and user icon paths under bin/ still resolve. -->
<ElectronPaParams>$(ElectronPaParams) -c.directories.app &quot;app&quot;</ElectronPaParams>
</PropertyGroup>
<PropertyGroup>
<_NpxCmd>npx electron-builder --config=./$(ElectronBuilderJson) --$(ElectronPlatform) --$(ElectronArch) -c.electronVersion=$(ElectronVersion) -c.directories.output &quot;$(ElectronPublishUrlFullPath)&quot; $(ElectronPaParams)</_NpxCmd>
<_NpxCmd>npx electron-builder --config=./$(ElectronBuilderJson)</_NpxCmd>
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronPlatform)</_NpxCmd>
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronArch)</_NpxCmd>
<_NpxCmd>$(_NpxCmd) -c.electronVersion=$(ElectronVersion) -c.directories.output &quot;$(ElectronPublishUrlFullPath)&quot; $(ElectronPaParams)</_NpxCmd>
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'</_NpxCmd>
</PropertyGroup>

View File

@@ -437,6 +437,8 @@
<EnumValue Name="40.10.0" DisplayName="40.10.0" />
<EnumValue Name="40.10.1" DisplayName="40.10.1" />
<EnumValue Name="40.10.2" DisplayName="40.10.2" />
<EnumValue Name="40.10.3" DisplayName="40.10.3" />
<EnumValue Name="40.10.4" DisplayName="40.10.4" />
<EnumValue Name="41.0.0" DisplayName="41.0.0" />
<EnumValue Name="41.0.1" DisplayName="41.0.1" />
<EnumValue Name="41.0.2" DisplayName="41.0.2" />
@@ -456,6 +458,8 @@
<EnumValue Name="41.6.1" DisplayName="41.6.1" />
<EnumValue Name="41.7.0" DisplayName="41.7.0" />
<EnumValue Name="41.7.1" DisplayName="41.7.1" />
<EnumValue Name="41.7.2" DisplayName="41.7.2" />
<EnumValue Name="41.8.0" DisplayName="41.8.0" />
<EnumValue Name="42.0.0" DisplayName="42.0.0" />
<EnumValue Name="42.0.1" DisplayName="42.0.1" />
<EnumValue Name="42.1.0" DisplayName="42.1.0" />
@@ -464,6 +468,8 @@
<EnumValue Name="42.3.1" DisplayName="42.3.1" />
<EnumValue Name="42.3.2" DisplayName="42.3.2" />
<EnumValue Name="42.3.3" DisplayName="42.3.3" />
<EnumValue Name="42.4.0" DisplayName="42.4.0" />
<EnumValue Name="42.4.1" DisplayName="42.4.1" />
</EnumProperty>
<EnumProperty Name="ElectronBuilderVersion"
@@ -566,7 +572,7 @@
<StringProperty Name="ElectronIcon"
DisplayName="App Icon"
Description="Choose a ICO file to be used as application icon"
Description="Choose an icon path for the app (e.g. .ico, .icns, or modern macOS .icon folder)"
Subtype="File"
Category="General">
<StringProperty.DataSource>
@@ -575,7 +581,7 @@
<StringProperty.ValueEditors>
<ValueEditor EditorType="FilePath">
<ValueEditor.Metadata>
<NameValuePair Name="FileTypeFilter" Value="Icon files (*.ico)|*.ico|All files (*.*)|*.*" />
<NameValuePair Name="FileTypeFilter" Value="Icon files (*.ico,*.icns)|*.ico;*.icns|All files (*.*)|*.*" />
</ValueEditor.Metadata>
</ValueEditor>
</StringProperty.ValueEditors>

View File

@@ -0,0 +1,19 @@
{
"name": "electron-builder-host",
"version": "$(Version)",
"private": true,
"build": {
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {
"entry": { "Name": "$(Title)" }
}
}
}
}

View File

@@ -1,20 +1,6 @@
{
"name": "$(ElectronPackageId)",
"productName": "$(ElectronTitle)",
"build": {
"appId": "$(ElectronPackageId)",
"linux": {
"desktop": {
"entry": { "Name": "$(Title)" }
},
"executableName": "$(ElectronPackageId)"
},
"deb": {
"desktop": {
"entry": { "Name": "$(Title)" }
}
}
},
"description": "$(Description)",
"version": "$(Version)",
"main": "main.js",

View File

@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.5.1</Version>
<Version>0.5.2</Version>
<PackageNamePrefix>ElectronNET.Core</PackageNamePrefix>
<Authors>Gregor Biswanger, Florian Rappl, softworkz</Authors>
<Product>Electron.NET</Product>