mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
fis.airapp相关运行时修复以及打包构建工作流修复
This commit is contained in:
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -244,6 +244,27 @@ jobs:
|
||||
-AssertClean
|
||||
shell: pwsh
|
||||
|
||||
- name: Verify Windows app host payload
|
||||
run: |
|
||||
$version = "${{ needs.prepare.outputs.version }}"
|
||||
$arch = "${{ matrix.arch }}"
|
||||
$publishDir = "publish/windows-$arch"
|
||||
$appDir = Join-Path $publishDir "app-$version"
|
||||
|
||||
$requiredFiles = @(
|
||||
(Join-Path $publishDir "LanMountainDesktop.Launcher.exe"),
|
||||
(Join-Path $appDir "LanMountainDesktop.exe"),
|
||||
(Join-Path $appDir "LanMountainDesktop.AirAppHost.exe")
|
||||
)
|
||||
|
||||
foreach ($path in $requiredFiles) {
|
||||
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
|
||||
Write-Error "Required release payload file is missing: $path"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
shell: pwsh
|
||||
|
||||
- name: Install Inno Setup and 7z
|
||||
run: |
|
||||
choco install innosetup -y --no-progress
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace LanMountainDesktop.Launcher.Services;
|
||||
|
||||
internal sealed class LauncherFlowCoordinator
|
||||
{
|
||||
private static readonly TimeSpan StartupSoftTimeout = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan StartupHardTimeout = TimeSpan.FromSeconds(30);
|
||||
private static readonly TimeSpan StartupSoftTimeout = TimeSpan.FromSeconds(30);
|
||||
private static readonly TimeSpan StartupHardTimeout = TimeSpan.FromSeconds(120);
|
||||
private static readonly string SoftTimeoutStatusMessage = Strings.Coordinator_SlowDeviceMessage;
|
||||
private static readonly string SoftTimeoutDetailsMessage = Strings.Coordinator_RunningHostMessage;
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class LauncherStartupTimeoutPolicyTests
|
||||
{
|
||||
[Fact]
|
||||
public void LauncherStartupTimeouts_MatchSlowStartupContract()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.Launcher", "Services", "LauncherFlowCoordinator.cs");
|
||||
|
||||
Assert.Contains("StartupSoftTimeout = TimeSpan.FromSeconds(30)", source);
|
||||
Assert.Contains("StartupHardTimeout = TimeSpan.FromSeconds(120)", source);
|
||||
Assert.DoesNotContain("StartupHardTimeout = TimeSpan.FromSeconds(30)", source);
|
||||
}
|
||||
|
||||
private static string ReadRepositoryFile(params string[] pathParts)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "LanMountainDesktop.slnx")))
|
||||
{
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
if (directory is null)
|
||||
{
|
||||
throw new DirectoryNotFoundException("Unable to locate repository root.");
|
||||
}
|
||||
|
||||
return File.ReadAllText(Path.Combine([directory.FullName, .. pathParts]));
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,26 @@ public sealed class PackagingRuntimePolicyTests
|
||||
Assert.Contains("System.Private.CoreLib.dll", script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WindowsPayloadGuard_RequiresLauncherMainAndAirAppHost()
|
||||
{
|
||||
var script = ReadRepositoryFile("LanMountainDesktop", "scripts", "Optimize-PublishPayload.ps1");
|
||||
|
||||
Assert.Contains("Assert-WindowsPayloadContainsRequiredHosts", script);
|
||||
Assert.Contains("LanMountainDesktop.Launcher.exe", script);
|
||||
Assert.Contains("LanMountainDesktop.exe", script);
|
||||
Assert.Contains("LanMountainDesktop.AirAppHost.exe", script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReleaseWorkflow_VerifiesAirAppHostBeforePublishingInstaller()
|
||||
{
|
||||
var workflow = ReadRepositoryFile(".github", "workflows", "release.yml");
|
||||
|
||||
Assert.Contains("Verify Windows app host payload", workflow);
|
||||
Assert.Contains("LanMountainDesktop.AirAppHost.exe", workflow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Installer_DownloadsArchitectureSpecificDesktopRuntime()
|
||||
{
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Threading.Tasks;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Update;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
using LanMountainDesktop.Settings.Core;
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Services.Settings
|
||||
{
|
||||
@@ -356,8 +358,21 @@ public interface IPrivacySettingsService
|
||||
|
||||
public interface IUpdateSettingsService
|
||||
{
|
||||
UpdatePhase CurrentPhase { get; }
|
||||
event Action<UpdatePhase>? PhaseChanged;
|
||||
event Action<UpdateProgressReport>? ProgressChanged;
|
||||
|
||||
UpdateSettingsState Get();
|
||||
void Save(UpdateSettingsState state);
|
||||
Task<UpdateCheckReport> CheckAsync(CancellationToken cancellationToken = default);
|
||||
Task<LanMountainDesktop.Services.Update.DownloadResult> DownloadAsync(CancellationToken cancellationToken = default);
|
||||
Task<InstallResult> InstallAsync(CancellationToken cancellationToken = default);
|
||||
Task RollbackAsync(CancellationToken cancellationToken = default);
|
||||
Task PauseAsync();
|
||||
Task<LanMountainDesktop.Services.Update.DownloadResult> ResumeAsync(CancellationToken cancellationToken = default);
|
||||
Task CancelAsync();
|
||||
Task AutoCheckIfEnabledAsync(CancellationToken cancellationToken = default);
|
||||
bool TryApplyOnExit();
|
||||
Task<UpdateCheckResult> CheckForUpdatesAsync(Version currentVersion, bool includePrerelease, CancellationToken cancellationToken = default);
|
||||
Task<UpdateCheckResult> ForceCheckForUpdatesAsync(Version currentVersion, bool includePrerelease, CancellationToken cancellationToken = default);
|
||||
Task<PlondsUpdatePayload?> GetPlondsUpdatePayloadAsync(Version currentVersion, bool includePrerelease, bool isForce = false, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -10,8 +10,10 @@ using Avalonia.Media.Imaging;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Update;
|
||||
using LanMountainDesktop.Settings.Core;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Services.Settings;
|
||||
|
||||
@@ -784,10 +786,40 @@ internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposabl
|
||||
private readonly GitHubReleaseUpdateService _githubReleaseUpdateService = new("wwiinnddyy", "LanMountainDesktop");
|
||||
private readonly PlondsStaticUpdateService _plondsStaticUpdateService = new();
|
||||
private readonly PlondsReleaseUpdateService _plondsReleaseUpdateService = new();
|
||||
private readonly Lazy<UpdateOrchestrator> _orchestrator;
|
||||
|
||||
public UpdateSettingsService(ISettingsService settingsService)
|
||||
public UpdateSettingsService(ISettingsService settingsService, Func<UpdateOrchestrator>? orchestratorFactory = null)
|
||||
{
|
||||
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
|
||||
_orchestrator = new Lazy<UpdateOrchestrator>(
|
||||
orchestratorFactory ?? HostUpdateOrchestratorProvider.GetOrCreate,
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
public UpdatePhase CurrentPhase => _orchestrator.Value.CurrentPhase;
|
||||
|
||||
public event Action<UpdatePhase>? PhaseChanged
|
||||
{
|
||||
add => _orchestrator.Value.PhaseChanged += value;
|
||||
remove
|
||||
{
|
||||
if (_orchestrator.IsValueCreated)
|
||||
{
|
||||
_orchestrator.Value.PhaseChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<UpdateProgressReport>? ProgressChanged
|
||||
{
|
||||
add => _orchestrator.Value.ProgressChanged += value;
|
||||
remove
|
||||
{
|
||||
if (_orchestrator.IsValueCreated)
|
||||
{
|
||||
_orchestrator.Value.ProgressChanged -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateSettingsState Get()
|
||||
@@ -862,6 +894,51 @@ internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposabl
|
||||
]);
|
||||
}
|
||||
|
||||
public Task<UpdateCheckReport> CheckAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.CheckAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<LanMountainDesktop.Services.Update.DownloadResult> DownloadAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.DownloadAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task<InstallResult> InstallAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.InstallAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task RollbackAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.RollbackAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task PauseAsync()
|
||||
{
|
||||
return _orchestrator.Value.PauseAsync();
|
||||
}
|
||||
|
||||
public Task<LanMountainDesktop.Services.Update.DownloadResult> ResumeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.ResumeAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public Task CancelAsync()
|
||||
{
|
||||
return _orchestrator.Value.CancelAsync();
|
||||
}
|
||||
|
||||
public Task AutoCheckIfEnabledAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _orchestrator.Value.AutoCheckIfEnabledAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public bool TryApplyOnExit()
|
||||
{
|
||||
return _orchestrator.Value.TryApplyOnExit();
|
||||
}
|
||||
|
||||
public Task<UpdateCheckResult> CheckForUpdatesAsync(
|
||||
Version currentVersion,
|
||||
bool includePrerelease,
|
||||
@@ -945,6 +1022,15 @@ internal sealed class UpdateSettingsService : IUpdateSettingsService, IDisposabl
|
||||
bool isForce,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var source = UpdateSettingsValues.NormalizeDownloadSource(Get().UpdateDownloadSource);
|
||||
if (string.Equals(source, UpdateSettingsValues.DownloadSourceGitHub, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(source, UpdateSettingsValues.DownloadSourceGhProxy, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return isForce
|
||||
? await _githubReleaseUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
||||
: await _githubReleaseUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
||||
}
|
||||
|
||||
var staticResult = isForce
|
||||
? await _plondsStaticUpdateService.ForceCheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken)
|
||||
: await _plondsStaticUpdateService.CheckForUpdatesAsync(currentVersion, includePrerelease, cancellationToken);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.Shared.Contracts.Update;
|
||||
|
||||
namespace LanMountainDesktop.Services.Update;
|
||||
|
||||
internal sealed class SettingsUpdateManifestProvider : IUpdateManifestProvider
|
||||
{
|
||||
private readonly ISettingsFacadeService _settingsFacade;
|
||||
private readonly IUpdateManifestProvider _plondsWithFallback;
|
||||
private readonly IUpdateManifestProvider _github;
|
||||
|
||||
public SettingsUpdateManifestProvider(
|
||||
ISettingsFacadeService settingsFacade,
|
||||
IUpdateManifestProvider plonds,
|
||||
IUpdateManifestProvider github)
|
||||
{
|
||||
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
||||
_github = github ?? throw new ArgumentNullException(nameof(github));
|
||||
_plondsWithFallback = new CompositeManifestProvider(plonds ?? throw new ArgumentNullException(nameof(plonds)), _github);
|
||||
}
|
||||
|
||||
public string ProviderName => "settings-selected-update-source";
|
||||
|
||||
public Task<UpdateManifest?> GetLatestAsync(
|
||||
string channel,
|
||||
string platform,
|
||||
Version currentVersion,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return SelectProvider().GetLatestAsync(channel, platform, currentVersion, ct);
|
||||
}
|
||||
|
||||
public Task<UpdateManifest?> GetByVersionAsync(
|
||||
string version,
|
||||
string channel,
|
||||
string platform,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return SelectProvider().GetByVersionAsync(version, channel, platform, ct);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<UpdateManifest>> GetIncrementalChainAsync(
|
||||
string channel,
|
||||
string platform,
|
||||
Version fromVersion,
|
||||
Version toVersion,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return SelectProvider().GetIncrementalChainAsync(channel, platform, fromVersion, toVersion, ct);
|
||||
}
|
||||
|
||||
private IUpdateManifestProvider SelectProvider()
|
||||
{
|
||||
var source = UpdateSettingsValues.NormalizeDownloadSource(_settingsFacade.Update.Get().UpdateDownloadSource);
|
||||
return string.Equals(source, UpdateSettingsValues.DownloadSourceGitHub, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(source, UpdateSettingsValues.DownloadSourceGhProxy, StringComparison.OrdinalIgnoreCase)
|
||||
? _github
|
||||
: _plondsWithFallback;
|
||||
}
|
||||
}
|
||||
@@ -232,6 +232,7 @@ internal sealed class UpdateDownloadEngine
|
||||
UpdateManifest manifest,
|
||||
string destinationPath,
|
||||
int maxThreads,
|
||||
string? downloadSource,
|
||||
IProgress<DownloadProgressReport>? progress,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -281,7 +282,7 @@ internal sealed class UpdateDownloadEngine
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var result = await _downloadService.DownloadAsync(
|
||||
mirror.Url,
|
||||
ApplyDownloadSource(mirror.Url, downloadSource),
|
||||
destinationPath,
|
||||
new DownloadOptions(MaxParallelSegments: Math.Max(1, maxThreads)),
|
||||
downloadProgress,
|
||||
@@ -386,6 +387,22 @@ internal sealed class UpdateDownloadEngine
|
||||
throw lastError!;
|
||||
}
|
||||
|
||||
internal static string ApplyDownloadSource(string browserDownloadUrl, string? downloadSource)
|
||||
{
|
||||
if (!string.Equals(
|
||||
UpdateSettingsValues.NormalizeDownloadSource(downloadSource),
|
||||
UpdateSettingsValues.DownloadSourceGhProxy,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return browserDownloadUrl;
|
||||
}
|
||||
|
||||
var normalizedBase = UpdateSettingsValues.DefaultGhProxyBaseUrl.TrimEnd('/') + "/";
|
||||
return browserDownloadUrl.StartsWith(normalizedBase, StringComparison.OrdinalIgnoreCase)
|
||||
? browserDownloadUrl
|
||||
: normalizedBase + browserDownloadUrl;
|
||||
}
|
||||
|
||||
private static async Task<string> ComputeFileSha256Async(string filePath, CancellationToken ct)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true);
|
||||
|
||||
@@ -96,6 +96,13 @@ internal static class UpdateManifestMapper
|
||||
ArchiveSha256: null,
|
||||
Metadata: null));
|
||||
|
||||
mirrors.Add(new UpdateMirrorAsset(
|
||||
Platform: platform,
|
||||
Url: installerAsset.BrowserDownloadUrl,
|
||||
Name: installerAsset.Name,
|
||||
Sha256: installerAsset.Sha256,
|
||||
Size: installerAsset.SizeBytes));
|
||||
|
||||
foreach (var asset in release.Assets)
|
||||
{
|
||||
if (IsInstallerAsset(asset) && asset != installerAsset)
|
||||
|
||||
@@ -25,13 +25,13 @@ internal static class HostUpdateOrchestratorProvider
|
||||
|
||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||
var githubProvider = new GithubReleaseManifestProvider("wwiinnddyy", "LanMountainDesktop");
|
||||
var staticProvider = new PlondsApiManifestProvider("https://api.classisland.tech");
|
||||
var compositeProvider = new CompositeManifestProvider(staticProvider, githubProvider);
|
||||
var plondsProvider = new PlondsApiManifestProvider("https://api.classisland.tech");
|
||||
var manifestProvider = new SettingsUpdateManifestProvider(settingsFacade, plondsProvider, githubProvider);
|
||||
var httpClient = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||
var downloadEngine = new UpdateDownloadEngine(compositeProvider, new ResumableDownloadService(httpClient));
|
||||
var downloadEngine = new UpdateDownloadEngine(manifestProvider, new ResumableDownloadService(httpClient));
|
||||
var installGateway = new UpdateInstallGateway();
|
||||
var stateStore = new UpdateStateStore(settingsFacade);
|
||||
_instance = new UpdateOrchestrator(compositeProvider, downloadEngine, installGateway, stateStore);
|
||||
_instance = new UpdateOrchestrator(manifestProvider, downloadEngine, installGateway, stateStore);
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
@@ -106,8 +106,7 @@ public sealed class UpdateOrchestrator : IDisposable
|
||||
|
||||
var settings = _stateStore.GetSettings();
|
||||
var channel = UpdateSettingsValues.NormalizeChannel(settings.UpdateChannel);
|
||||
var currentVersionText = _stateStore.GetSettings().PendingUpdateVersion
|
||||
?? AppVersionProvider.ResolveForCurrentProcess().Version;
|
||||
var currentVersionText = AppVersionProvider.ResolveForCurrentProcess().Version;
|
||||
|
||||
if (!TryParseVersion(currentVersionText, out var currentVersion))
|
||||
{
|
||||
@@ -166,12 +165,14 @@ public sealed class UpdateOrchestrator : IDisposable
|
||||
if (manifest is null)
|
||||
{
|
||||
_stateStore.TransitionTo(UpdatePhase.Checked);
|
||||
SaveLastChecked();
|
||||
return new UpdateCheckReport(
|
||||
false, null, currentVersionText, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
_stateStore.PendingManifest = manifest;
|
||||
_stateStore.TransitionTo(UpdatePhase.Checked);
|
||||
SaveLastChecked();
|
||||
|
||||
long? totalBytes = manifest.IsDelta ? manifest.EstimatedDeltaBytes : null;
|
||||
long? installerBytes = manifest.InstallerMirrors?.Count > 0
|
||||
@@ -262,6 +263,7 @@ public sealed class UpdateOrchestrator : IDisposable
|
||||
manifest,
|
||||
destinationPath,
|
||||
maxThreads,
|
||||
settings.UpdateDownloadSource,
|
||||
downloadProgress,
|
||||
operationToken);
|
||||
}
|
||||
@@ -569,15 +571,12 @@ public sealed class UpdateOrchestrator : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
var manifest = _stateStore.PendingManifest;
|
||||
if (manifest is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var launcherRoot = UpdatePaths.ResolveLauncherRoot(AppContext.BaseDirectory);
|
||||
var manifest = _stateStore.PendingManifest;
|
||||
var deploymentLock = DeploymentLockService.ReadLock(launcherRoot);
|
||||
|
||||
if (manifest.IsDelta)
|
||||
if (manifest?.IsDelta == true ||
|
||||
string.Equals(deploymentLock?.Kind, "delta", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AppLogger.Info("UpdateOrchestrator", "Delta update pending. Launching Launcher to apply on exit.");
|
||||
var launcherPath = LauncherPathResolver.ResolveLauncherExecutablePath();
|
||||
@@ -638,6 +637,15 @@ public sealed class UpdateOrchestrator : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveLastChecked()
|
||||
{
|
||||
var state = _stateStore.GetSettings();
|
||||
_stateStore.SaveSettings(state with
|
||||
{
|
||||
LastUpdateCheckUtcMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
private static void CleanupIncomingArtifacts(string launcherRoot)
|
||||
{
|
||||
var incomingDir = UpdatePaths.GetIncomingDirectory(launcherRoot);
|
||||
|
||||
@@ -201,6 +201,52 @@ function Assert-WindowsPayloadClean {
|
||||
Write-Host "Windows payload guard passed for $Rid."
|
||||
}
|
||||
|
||||
function Assert-WindowsPayloadContainsRequiredHosts {
|
||||
param([Parameter(Mandatory = $true)][string]$Root)
|
||||
|
||||
$violations = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
$launcherPath = Join-Path $Root "LanMountainDesktop.Launcher.exe"
|
||||
if (-not (Test-Path -LiteralPath $launcherPath -PathType Leaf)) {
|
||||
$violations.Add("LanMountainDesktop.Launcher.exe")
|
||||
}
|
||||
|
||||
$deploymentDirs = @(Get-ChildItem -LiteralPath $Root -Directory -Filter "app-*" -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
-not (Test-Path -LiteralPath (Join-Path $_.FullName ".partial")) -and
|
||||
-not (Test-Path -LiteralPath (Join-Path $_.FullName ".destroy"))
|
||||
})
|
||||
|
||||
if ($deploymentDirs.Count -eq 0) {
|
||||
$violations.Add("app-*/")
|
||||
}
|
||||
|
||||
foreach ($deploymentDir in $deploymentDirs) {
|
||||
$mainHostPath = Join-Path $deploymentDir.FullName "LanMountainDesktop.exe"
|
||||
if (-not (Test-Path -LiteralPath $mainHostPath -PathType Leaf)) {
|
||||
$violations.Add((Join-Path $deploymentDir.Name "LanMountainDesktop.exe"))
|
||||
}
|
||||
|
||||
$airAppHostCandidates = @(
|
||||
(Join-Path $deploymentDir.FullName "LanMountainDesktop.AirAppHost.exe"),
|
||||
(Join-Path $deploymentDir.FullName "LanMountainDesktop.AirAppHost.dll"),
|
||||
(Join-Path (Join-Path $deploymentDir.FullName "AirAppHost") "LanMountainDesktop.AirAppHost.exe"),
|
||||
(Join-Path (Join-Path $deploymentDir.FullName "AirAppHost") "LanMountainDesktop.AirAppHost.dll")
|
||||
)
|
||||
|
||||
if (-not ($airAppHostCandidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1)) {
|
||||
$violations.Add((Join-Path $deploymentDir.Name "LanMountainDesktop.AirAppHost.exe"))
|
||||
}
|
||||
}
|
||||
|
||||
if ($violations.Count -gt 0) {
|
||||
$sample = ($violations | Select-Object -First 50) -join [Environment]::NewLine
|
||||
throw "Windows publish payload is missing required Launcher/Main/AirAppHost files:$([Environment]::NewLine)$sample"
|
||||
}
|
||||
|
||||
Write-Host "Windows required host guard passed."
|
||||
}
|
||||
|
||||
$resolvedPublishDir = [System.IO.Path]::GetFullPath($PublishDir)
|
||||
if (-not (Test-Path -LiteralPath $resolvedPublishDir)) {
|
||||
throw "Publish directory not found: $resolvedPublishDir"
|
||||
@@ -213,4 +259,7 @@ Write-PayloadAudit -Root $resolvedPublishDir
|
||||
|
||||
if ($AssertClean) {
|
||||
Assert-WindowsPayloadClean -Root $resolvedPublishDir -Rid $RuntimeIdentifier
|
||||
if ($RuntimeIdentifier -like "win-*") {
|
||||
Assert-WindowsPayloadContainsRequiredHosts -Root $resolvedPublishDir
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user