diff --git a/Changelog.md b/Changelog.md
index 3df8cdc..14c4706 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -10,6 +10,8 @@
- Fixed cross-compilation behavior on same platform (#1098) @epsnm
- Fixed resolution of unrelated target (#1099) @epsnm
- Added target framework customization (#1095) @epsnm
+- Added configurable Electron root directory for custom packaging layouts (#1106) @DYH1319
+- Added build extensibility properties `ElectronSkipExecCommands` and `ElectronIntermediatePublishDir` (#1106) @DYH1319
# 0.5.2
diff --git a/docs/Using/Configuration.md b/docs/Using/Configuration.md
index dd16fc5..e37bf5c 100644
--- a/docs/Using/Configuration.md
+++ b/docs/Using/Configuration.md
@@ -28,9 +28,53 @@ These are the current default values when you don't make any changes:
$(MSBuildProjectName.Replace(".", "-").ToLower())
electron-builder.json
$(MSBuildProjectName)
+ ../..
+ false
```
+### Custom Packaging Layout
+
+`ElectronRootDir` tells the .NET app where to find the Electron binary when it is started first (DotNet-First startup of a packaged app). The path is resolved relative to the directory of the .NET executable; absolute paths are used as-is.
+
+The default `../..` matches the standard Electron-First packaging layout produced by `dotnet publish`, where the .NET app is placed in `resources/bin` of the Electron package:
+
+```
+/
+ MyApp # Electron executable
+ resources/
+ bin/
+ MyApp # .NET executable
+```
+
+If your own packaging pipeline places the .NET executable at the package root and Electron in a subdirectory, set `ElectronRootDir` accordingly:
+
+```xml
+electron
+```
+
+```
+/
+ MyApp # .NET executable
+ electron/
+ MyApp # Electron executable
+ resources/
+ locales/
+```
+
+The property is also used to detect whether the app runs packaged or unpackaged, so it must point to the directory that actually contains the Electron binary.
+
+### Build Extensibility
+
+These properties are useful when Electron.NET is embedded into a custom build or CI pipeline:
+
+| Property | Default | Description |
+| --- | --- | --- |
+| `ElectronSkipExecCommands` | `false` | Skips all `npm`/`npx` invocations of the Electron build and publish targets. Use it when the dependencies and the Electron distribution are provided by the pipeline itself (for example from a cache or an offline mirror). |
+| `ElectronIntermediatePublishDir` | `$(IntermediateOutputPath)PubTmp\` | Overrides the intermediate directory the .NET app is published to before it is assembled into the Electron package. |
+
+`ElectronPackageId` and `Title` only fall back to the project name when they are not already set, so both can be defined by a `Directory.Build.props` or passed on the command line.
+
### Relation to package.json
ElectronNET.Core does not work with an `electron-manifest.json` file anymore.
diff --git a/docs/Using/Package-Building.md b/docs/Using/Package-Building.md
index 6b6325e..d6f7473 100644
--- a/docs/Using/Package-Building.md
+++ b/docs/Using/Package-Building.md
@@ -222,6 +222,15 @@ The publish process will:
> macOS builds can't be created on Windows machines because they require symlinks that aren't supported on Windows (per [this Electron issue](https://github.com/electron-userland/electron-packager/issues/71)). macOS builds can be produced on either Linux or macOS machines.
+## Custom Packaging Pipelines
+
+The default layout is Electron-First: electron-builder produces the Electron package and the .NET app is placed in `resources/bin`. If you assemble the package yourself and put the .NET executable at the package root with Electron in a subdirectory, set `ElectronRootDir` so the .NET app can locate the Electron binary at runtime.
+
+For pipelines that provide the npm dependencies themselves, `ElectronSkipExecCommands` suppresses all `npm`/`npx` invocations, and `ElectronIntermediatePublishDir` redirects the intermediate publish output.
+
+See [Configuration](Configuration.md#custom-packaging-layout) for details.
+
+
## 🚀 Next Steps
- **[Startup Methods](Startup-Methods.md)** - Understanding different launch modes for packaged apps
diff --git a/src/ElectronNET.API/Common/ProcessRunner.cs b/src/ElectronNET.API/Common/ProcessRunner.cs
index d561020..2d44e92 100644
--- a/src/ElectronNET.API/Common/ProcessRunner.cs
+++ b/src/ElectronNET.API/Common/ProcessRunner.cs
@@ -37,10 +37,18 @@
public ProcessRunner(string name)
{
this.Name = name;
+ this.ExitWaitTimeout = DefaultExitWaitTimeout;
}
public event EventHandler ProcessExited;
+ /// Gets or sets the value is initialized with.
+ public static int DefaultExitWaitTimeout { get; set; } = 5000;
+
+ /// Gets or sets the time in milliseconds to wait for the process and its I/O to finish
+ /// after the exited event has been raised. Values of zero or less wait indefinitely.
+ public int ExitWaitTimeout { get; set; }
+
public bool IsDisposed { get; private set; }
private Process Process
@@ -498,8 +506,17 @@
{
try
{
+ var timeout = this.ExitWaitTimeout;
+
// This shouldn't throw here, but the mono process implementation doesn't always behave as it should.
- this.process.WaitForExit();
+ if (timeout > 0)
+ {
+ this.process.WaitForExit(timeout);
+ }
+ else
+ {
+ this.process.WaitForExit();
+ }
}
catch (Exception ex)
{
diff --git a/src/ElectronNET.API/Runtime/Data/BuildInfo.cs b/src/ElectronNET.API/Runtime/Data/BuildInfo.cs
index 75febe0..faefd12 100644
--- a/src/ElectronNET.API/Runtime/Data/BuildInfo.cs
+++ b/src/ElectronNET.API/Runtime/Data/BuildInfo.cs
@@ -6,6 +6,8 @@
public string ElectronVersion { get; internal set; }
+ public string ElectronRootDir { get; internal set; }
+
public string RuntimeIdentifier { get; internal set; }
public bool ElectronSingleInstance { get; internal set; }
diff --git a/src/ElectronNET.API/Runtime/Helpers/ElectronRootDirResolver.cs b/src/ElectronNET.API/Runtime/Helpers/ElectronRootDirResolver.cs
new file mode 100644
index 0000000..6d17a61
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/Helpers/ElectronRootDirResolver.cs
@@ -0,0 +1,31 @@
+namespace ElectronNET.Runtime.Helpers
+{
+ using System;
+ using System.IO;
+
+ ///
+ /// Resolves the directory that contains the Electron binary of a packaged application.
+ ///
+ internal static class ElectronRootDirResolver
+ {
+ /// The location of the Electron binary relative to the .NET application directory in the
+ /// default (Electron-first) layout, where the .NET application is placed in 'resources/bin'.
+ internal const string DefaultElectronRootDir = "../..";
+
+ /// Resolves the Electron root directory relative to the given .NET application directory.
+ /// The directory containing the .NET application.
+ /// The directory containing the Electron binary.
+ public static DirectoryInfo Resolve(DirectoryInfo baseDirectory)
+ {
+ var rootDir = ElectronNetRuntime.BuildInfo?.ElectronRootDir;
+
+ if (string.IsNullOrWhiteSpace(rootDir))
+ {
+ rootDir = DefaultElectronRootDir;
+ }
+
+ // An absolute ElectronRootDir is used as-is by Path.Combine.
+ return new DirectoryInfo(Path.GetFullPath(Path.Combine(baseDirectory.FullName, rootDir)));
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
index f5c5f54..ed814a2 100644
--- a/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
+++ b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
@@ -69,6 +69,15 @@
return false;
}
+ // Packaged, if the Electron binary is found where ElectronRootDir points to.
+ var electronExecutable = ElectronNetRuntime.ElectronExecutable;
+
+ if (!string.IsNullOrEmpty(electronExecutable) &&
+ File.Exists(Path.Combine(ElectronRootDirResolver.Resolve(dir).FullName, electronExecutable)))
+ {
+ return false;
+ }
+
if (dir.GetDirectories().Any(e => e.Name == ".electron"))
{
return true;
diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs
index 51c409e..55e502d 100644
--- a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs
+++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs
@@ -11,6 +11,7 @@
using System.Threading.Tasks;
using ElectronNET.Common;
using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
///
/// Launches and manages the Electron app process.
@@ -87,7 +88,7 @@
}
else
{
- dir = dir.Parent!.Parent!;
+ dir = ElectronRootDirResolver.Resolve(dir);
startCmd = Path.Combine(dir.FullName, this.electronBinaryName);
args = $"-dotnetpacked -electronforcedport={this.socketPort:D} " + this.extraArguments;
workingDir = dir.FullName;
diff --git a/src/ElectronNET.API/Runtime/StartupManager.cs b/src/ElectronNET.API/Runtime/StartupManager.cs
index 7817894..c945ef1 100644
--- a/src/ElectronNET.API/Runtime/StartupManager.cs
+++ b/src/ElectronNET.API/Runtime/StartupManager.cs
@@ -178,6 +178,7 @@
{
buildInfo.ElectronExecutable = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronExecutable))?.Value;
buildInfo.ElectronVersion = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronVersion))?.Value;
+ buildInfo.ElectronRootDir = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.ElectronRootDir))?.Value;
buildInfo.RuntimeIdentifier = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.RuntimeIdentifier))?.Value;
buildInfo.Title = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Title))?.Value;
buildInfo.Version = attributes.FirstOrDefault(e => e.Key == nameof(buildInfo.Version))?.Value;
diff --git a/src/ElectronNET/build/ElectronNET.Build.dll b/src/ElectronNET/build/ElectronNET.Build.dll
index c850ff3..0455b56 100644
Binary files a/src/ElectronNET/build/ElectronNET.Build.dll and b/src/ElectronNET/build/ElectronNET.Build.dll differ
diff --git a/src/ElectronNET/build/ElectronNET.Core.props b/src/ElectronNET/build/ElectronNET.Core.props
index 2291e3d..4b4eed4 100644
--- a/src/ElectronNET/build/ElectronNET.Core.props
+++ b/src/ElectronNET/build/ElectronNET.Core.props
@@ -11,9 +11,14 @@
- $(MSBuildProjectName.Replace(".", "-").ToLower())
+ $(MSBuildProjectName.Replace(".", "-").ToLower())
electron-builder.json
- $(MSBuildProjectName)
+ $(MSBuildProjectName)
+
+ ../..
+
+ false
diff --git a/src/ElectronNET/build/ElectronNET.Core.targets b/src/ElectronNET/build/ElectronNET.Core.targets
index 962ed6f..753b130 100644
--- a/src/ElectronNET/build/ElectronNET.Core.targets
+++ b/src/ElectronNET/build/ElectronNET.Core.targets
@@ -19,6 +19,7 @@
+
diff --git a/src/ElectronNET/build/ElectronNET.LateImport.targets b/src/ElectronNET/build/ElectronNET.LateImport.targets
index 5e9d965..5866c7b 100644
--- a/src/ElectronNET/build/ElectronNET.LateImport.targets
+++ b/src/ElectronNET/build/ElectronNET.LateImport.targets
@@ -170,7 +170,9 @@
<_NonIntermediatePublishDir>$(PublishDir)
$(PublishDir)
- $(IntermediateOutputPath)PubTmp\
+
+ $(ElectronIntermediatePublishDir)
+ $(IntermediateOutputPath)PubTmp\
@@ -459,13 +461,13 @@
-
+
-
+
-
+
<_NpmCmd>node node_modules/electron/install.js
@@ -474,13 +476,13 @@
<_NpmCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmCmd)'
-
+
-
+
-
+
@@ -615,11 +617,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpmAppDepsCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpmAppDepsCmd)'
-
+
-
+
@@ -673,11 +675,11 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'
-
+
-
+
diff --git a/src/ElectronNET/build/ElectronNETRules.Project.xaml b/src/ElectronNET/build/ElectronNETRules.Project.xaml
index 0331ae8..4782916 100644
--- a/src/ElectronNET/build/ElectronNETRules.Project.xaml
+++ b/src/ElectronNET/build/ElectronNETRules.Project.xaml
@@ -553,6 +553,24 @@
Category="General">
+
+
+
+
+
+
+
+
+