From 549ed746c8ac93b55beace5a5118bf28918b1828 Mon Sep 17 00:00:00 2001
From: Zaldaryon <273555259+Zaldaryon@users.noreply.github.com>
Date: Wed, 16 Sep 2026 07:49:01 -0300
Subject: [PATCH] fix(installer): resolve Windows source script paths
---
.../BuildLayerTests.cs | 33 ++++++
.../SourceAcquisitionTests.cs | 109 ++++++++++++++++++
Optimum.Bootstrap.Core/Build/RepoRoot.cs | 9 +-
.../Build/ScriptBuildDriver.cs | 11 +-
.../Build/SourceAcquisition.cs | 18 ++-
5 files changed, 167 insertions(+), 13 deletions(-)
diff --git a/Optimum.Bootstrap.Core.Tests/BuildLayerTests.cs b/Optimum.Bootstrap.Core.Tests/BuildLayerTests.cs
index 2f52c213..8333c227 100644
--- a/Optimum.Bootstrap.Core.Tests/BuildLayerTests.cs
+++ b/Optimum.Bootstrap.Core.Tests/BuildLayerTests.cs
@@ -201,3 +201,36 @@ public async Task AnUnspawnableStepExecutableFailsTheBuildInsteadOfHangingTheWiz
}
}
}
+
+public class ScriptBuildDriverWindowsCommandTests
+{
+ [Fact]
+ public void BootstrapCommandUsesAnAbsoluteScriptPathFromTheSourceRoot()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ string repo = Path.Combine(Path.GetTempPath(), "optimum-source");
+ BuildRequest request = new(repo, Path.Combine(Path.GetTempPath(), "optimum-output"));
+
+ var command = new ScriptBuildDriver(probe).BootstrapCommand(request);
+
+ Assert.Equal("-File", command.Args[0]);
+ Assert.Equal(Path.GetFullPath(Path.Combine(repo, "scripts", "bootstrap.ps1")), command.Args[1]);
+ Assert.True(Path.IsPathFullyQualified(command.Args[1]));
+ }
+
+ [Fact]
+ public void PackageCommandUsesAnAbsoluteScriptPathFromTheSourceRoot()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ string repo = Path.Combine(Path.GetTempPath(), "optimum-source");
+ string output = Path.Combine(Path.GetTempPath(), "optimum-output");
+ BuildRequest request = new(repo, output);
+
+ var command = new ScriptBuildDriver(probe).PackageCommand(request);
+
+ Assert.Equal("-File", command.Args[0]);
+ Assert.Equal(Path.GetFullPath(Path.Combine(repo, "scripts", "package.ps1")), command.Args[1]);
+ Assert.True(Path.IsPathFullyQualified(command.Args[1]));
+ Assert.Equal(output, command.Args[3]);
+ }
+}
diff --git a/Optimum.Bootstrap.Core.Tests/SourceAcquisitionTests.cs b/Optimum.Bootstrap.Core.Tests/SourceAcquisitionTests.cs
index 105366a9..6bcf935e 100644
--- a/Optimum.Bootstrap.Core.Tests/SourceAcquisitionTests.cs
+++ b/Optimum.Bootstrap.Core.Tests/SourceAcquisitionTests.cs
@@ -7,6 +7,46 @@ namespace Optimum.Bootstrap.Core.Tests;
public class SourceCacheTests
{
+ [Fact]
+ public void WindowsCacheWithoutScriptsIsNotUsable()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/cache/forks.json");
+
+ Assert.False(SourceCache.IsUsableCheckout(probe, "/cache"));
+ }
+
+ [Fact]
+ public void WindowsCacheWithOnlyUnixBootstrapScriptIsNotUsable()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/cache/forks.json");
+ probe.AddFile("/cache/scripts/bootstrap.sh");
+
+ Assert.False(SourceCache.IsUsableCheckout(probe, "/cache"));
+ }
+
+ [Fact]
+ public void WindowsCacheMissingPackagingScriptIsNotUsable()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/cache/forks.json");
+ probe.AddFile("/cache/scripts/bootstrap.ps1");
+
+ Assert.False(SourceCache.IsUsableCheckout(probe, "/cache"));
+ }
+
+ [Fact]
+ public void WindowsCacheWithBothRequiredScriptsIsUsable()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/cache/forks.json");
+ probe.AddFile("/cache/scripts/bootstrap.ps1");
+ probe.AddFile("/cache/scripts/package.ps1");
+
+ Assert.True(SourceCache.IsUsableCheckout(probe, "/cache"));
+ }
+
[Theory]
[InlineData("0.3.14", "v0.3.14")]
[InlineData("1.0.0", "v1.0.0")]
@@ -75,6 +115,40 @@ public void CloneArgumentsOmitTheBranchWhenThereIsNoTag()
}
}
+public class RepoRootTests
+{
+ [Fact]
+ public void WindowsDiscoveryRejectsAUnixOnlyCheckout()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/repo/forks.json");
+ probe.AddFile("/repo/scripts/bootstrap.sh");
+
+ Assert.Null(RepoRoot.Discover(probe, "/repo"));
+ }
+
+ [Fact]
+ public void WindowsDiscoveryReturnsTheCheckoutWithBothPowerShellScripts()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.AddFile("/repo/forks.json");
+ probe.AddFile("/repo/scripts/bootstrap.ps1");
+ probe.AddFile("/repo/scripts/package.ps1");
+
+ Assert.Equal("/repo", RepoRoot.Discover(probe, "/repo"));
+ }
+
+ [Fact]
+ public void UnixDiscoveryKeepsItsExistingBootstrapShellContract()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Linux };
+ probe.AddFile("/repo/forks.json");
+ probe.AddFile("/repo/scripts/bootstrap.sh");
+
+ Assert.Equal("/repo", RepoRoot.Discover(probe, "/repo"));
+ }
+}
+
public class GitSourceProviderTests
{
[Fact]
@@ -118,6 +192,41 @@ public async Task ReusesACachedCheckoutWithoutTouchingGit()
Assert.Equal(cached, result.RepoRoot);
}
+ [Fact]
+ public async Task DoesNotReuseAnIncompleteWindowsCheckout()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.Environment["LOCALAPPDATA"] = "/cache";
+ string cached = "/cache/optimum/src-v0.3.14";
+ probe.AddFile($"{cached}/forks.json");
+ probe.AddFile($"{cached}/scripts/bootstrap.sh");
+ // No Git on PATH: an incomplete cache must be rejected, not reused.
+
+ var result = await new GitSourceProvider(probe)
+ .EnsureAsync(new SourceRequest("0.3.14"), NullBuildObserver.Instance, CancellationToken.None);
+
+ Assert.False(result.Ok);
+ Assert.Equal(FailureReason.SourceUnavailable, result.Reason);
+ }
+
+ [Fact]
+ public async Task ReusesACompleteWindowsCheckoutFromTheSourceCache()
+ {
+ var probe = new FakeSystemProbe { Os = OsKind.Windows };
+ probe.Environment["LOCALAPPDATA"] = "/cache";
+ string cached = "/cache/optimum/src-v0.3.14";
+ probe.AddFile($"{cached}/forks.json");
+ probe.AddFile($"{cached}/scripts/bootstrap.ps1");
+ probe.AddFile($"{cached}/scripts/package.ps1");
+ // No Git on PATH: proves the complete cached source is the one used.
+
+ var result = await new GitSourceProvider(probe)
+ .EnsureAsync(new SourceRequest("0.3.14"), NullBuildObserver.Instance, CancellationToken.None);
+
+ Assert.True(result.Ok);
+ Assert.Equal(cached, result.RepoRoot);
+ }
+
[Fact]
public async Task FailsWithSourceUnavailableWhenGitIsMissing()
{
diff --git a/Optimum.Bootstrap.Core/Build/RepoRoot.cs b/Optimum.Bootstrap.Core/Build/RepoRoot.cs
index 47cbeb57..fc94e6d2 100644
--- a/Optimum.Bootstrap.Core/Build/RepoRoot.cs
+++ b/Optimum.Bootstrap.Core/Build/RepoRoot.cs
@@ -4,9 +4,9 @@ namespace Optimum.Bootstrap.Core.Build;
///
/// Finds the Optimum checkout the engine has to drive: the nearest directory at
-/// or above a starting point that holds forks.json next to
-/// scripts/bootstrap.sh. Both front ends need this because the build
-/// pipeline is still the shell scripts (INSTALLER-PLAN.md section 2).
+/// or above a starting point that holds the manifest and the scripts required
+/// by the probed platform. Both front ends need this because the build pipeline
+/// is still the platform scripts (INSTALLER-PLAN.md section 2).
///
public static class RepoRoot
{
@@ -18,8 +18,7 @@ public static class RepoRoot
for (string? dir = start; dir is not null; dir = Path.GetDirectoryName(dir))
{
- if (probe.FileExists(Path.Combine(dir, "forks.json"))
- && probe.FileExists(Path.Combine(dir, "scripts", "bootstrap.sh")))
+ if (SourceCache.IsUsableCheckout(probe, dir))
return dir;
}
diff --git a/Optimum.Bootstrap.Core/Build/ScriptBuildDriver.cs b/Optimum.Bootstrap.Core/Build/ScriptBuildDriver.cs
index d9447d4c..56be4571 100644
--- a/Optimum.Bootstrap.Core/Build/ScriptBuildDriver.cs
+++ b/Optimum.Bootstrap.Core/Build/ScriptBuildDriver.cs
@@ -111,11 +111,11 @@ public async Task RunAsync(
}
}
- private (string Exe, IReadOnlyList Args) BootstrapCommand(BuildRequest request)
+ internal (string Exe, IReadOnlyList Args) BootstrapCommand(BuildRequest request)
{
if (probe.Os == OsKind.Windows)
{
- List win = ["-File", "scripts/bootstrap.ps1"];
+ List win = ["-File", ScriptPath(request.RepoRoot, "bootstrap.ps1")];
if (request.ClientArchive is not null)
win.AddRange(["-ClientArchive", request.ClientArchive]);
if (request.Version is not null)
@@ -131,13 +131,13 @@ public async Task RunAsync(
return ("bash", unix);
}
- private (string Exe, IReadOnlyList Args) PackageCommand(BuildRequest request)
+ internal (string Exe, IReadOnlyList Args) PackageCommand(BuildRequest request)
{
string output = request.OutputDirectory;
switch (probe.Os)
{
case OsKind.Windows:
- List win = ["-File", "scripts/package.ps1", "-OutputDir", output];
+ List win = ["-File", ScriptPath(request.RepoRoot, "package.ps1"), "-OutputDir", output];
if (request.ClientArchive is not null) win.AddRange(["-ClientArchive", request.ClientArchive]);
return (PwshExecutable(), win);
case OsKind.MacOs:
@@ -154,6 +154,9 @@ public async Task RunAsync(
}
}
+ private static string ScriptPath(string repoRoot, string scriptName) =>
+ Path.GetFullPath(Path.Combine(repoRoot, "scripts", scriptName));
+
private string DotnetExecutable() => DotnetSdkProbe.Find(probe) ?? "dotnet";
///
diff --git a/Optimum.Bootstrap.Core/Build/SourceAcquisition.cs b/Optimum.Bootstrap.Core/Build/SourceAcquisition.cs
index b93ca7c1..6b134217 100644
--- a/Optimum.Bootstrap.Core/Build/SourceAcquisition.cs
+++ b/Optimum.Bootstrap.Core/Build/SourceAcquisition.cs
@@ -87,10 +87,20 @@ internal static string SanitizeVersion(string version)
return v.Length > 1 && v[0] == 'v' && char.IsDigit(v[1]) ? v : null;
}
- /// True when a directory holds the two files the pipeline needs.
- public static bool IsUsableCheckout(ISystemProbe probe, string directory) =>
- probe.FileExists(Path.Combine(directory, "forks.json"))
- && probe.FileExists(Path.Combine(directory, "scripts", "bootstrap.sh"));
+ ///
+ /// True when a directory holds the manifest and the scripts required by the
+ /// platform's build and packaging pipeline.
+ ///
+ public static bool IsUsableCheckout(ISystemProbe probe, string directory)
+ {
+ if (!probe.FileExists(Path.Combine(directory, "forks.json")))
+ return false;
+
+ return probe.Os == OsKind.Windows
+ ? probe.FileExists(Path.Combine(directory, "scripts", "bootstrap.ps1"))
+ && probe.FileExists(Path.Combine(directory, "scripts", "package.ps1"))
+ : probe.FileExists(Path.Combine(directory, "scripts", "bootstrap.sh"));
+ }
internal static IReadOnlyList CloneArguments(string? tagRef, string targetDirectory)
{