diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 3b78d2c..e8a0614 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -37,8 +37,27 @@ jobs:
trx-file-path: '${{ runner.temp }}/*.trx'
output-directory: '${{ runner.temp }}/vsplaylists'
+ aot-test:
+ name: AOT publish test
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - uses: actions/checkout@v6
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ global-json-file: global.json
+ - name: Install NativeAOT prerequisites
+ run: sudo apt-get install -y clang zlib1g-dev
+ - name: Publish AOT
+ run: dotnet publish IntelliTect.Multitool.AotTest/IntelliTect.Multitool.AotTest.csproj -r linux-x64 -c Release
+ - name: Run AOT binary
+ run: ./IntelliTect.Multitool.AotTest/bin/Release/net8.0/linux-x64/publish/IntelliTect.Multitool.AotTest
+
automerge:
- needs: [build-and-test]
+ needs: [build-and-test, aot-test]
runs-on: ubuntu-latest
permissions:
diff --git a/IntelliTect.Multitool.AotTest/IntelliTect.Multitool.AotTest.csproj b/IntelliTect.Multitool.AotTest/IntelliTect.Multitool.AotTest.csproj
new file mode 100644
index 0000000..eb9f66a
--- /dev/null
+++ b/IntelliTect.Multitool.AotTest/IntelliTect.Multitool.AotTest.csproj
@@ -0,0 +1,13 @@
+
+
+ Exe
+ net8.0
+ false
+ true
+ true
+
+
+
+
+
+
diff --git a/IntelliTect.Multitool.AotTest/Program.cs b/IntelliTect.Multitool.AotTest/Program.cs
new file mode 100644
index 0000000..0d040c2
--- /dev/null
+++ b/IntelliTect.Multitool.AotTest/Program.cs
@@ -0,0 +1,27 @@
+using IntelliTect.Multitool;
+using IntelliTect.Multitool.Extensions;
+
+// StringExtensions
+bool slugOk = "Hello, World!".CreateUrlSlug() == "hello-world";
+bool urlOk = "https://github.com/IntelliTect".ValidateUrlString();
+
+// SystemLinqExtensions
+string?[] items = ["a", null, "b", null, "c"];
+bool filterOk = items.WhereNotNull().Count() == 3;
+
+// ReleaseDateAttribute — returns null since no attribute on this assembly,
+// but exercises the AOT-safe (statically-known type) GetCustomAttribute code path
+DateTime? releaseDate = ReleaseDateAttribute.GetReleaseDate();
+bool attributeOk = releaseDate is null;
+
+if (!slugOk || !urlOk || !filterOk || !attributeOk)
+{
+ Console.Error.WriteLine("AOT test FAILED.");
+ return 1;
+}
+
+// RepositoryPaths is excluded: its static initializer reads a build-time temp file
+// that doesn't exist at AOT runtime. The class is AOT-compatible (file I/O + LINQ only).
+
+Console.WriteLine("AOT test passed.");
+return 0;
diff --git a/IntelliTect.Multitool.slnx b/IntelliTect.Multitool.slnx
index 937a092..a83787a 100644
--- a/IntelliTect.Multitool.slnx
+++ b/IntelliTect.Multitool.slnx
@@ -7,6 +7,7 @@
+
diff --git a/IntelliTect.Multitool/IntelliTect.Multitool.csproj b/IntelliTect.Multitool/IntelliTect.Multitool.csproj
index 4a73c0b..eb76d5e 100644
--- a/IntelliTect.Multitool/IntelliTect.Multitool.csproj
+++ b/IntelliTect.Multitool/IntelliTect.Multitool.csproj
@@ -1,6 +1,6 @@
- netstandard2.1
+ netstandard2.1;net8.0
Library
true
true
@@ -13,6 +13,7 @@
true
en
1.0.1
+ true
diff --git a/IntelliTect.Multitool/ReleaseDateAttribute.cs b/IntelliTect.Multitool/ReleaseDateAttribute.cs
index 7356c64..1acea3c 100644
--- a/IntelliTect.Multitool/ReleaseDateAttribute.cs
+++ b/IntelliTect.Multitool/ReleaseDateAttribute.cs
@@ -22,8 +22,7 @@ public class ReleaseDateAttribute(string utcDateString) : Attribute
/// The date time from the assembly attribute
public static DateTime? GetReleaseDate(Assembly? assembly = null)
{
- object[]? attribute = (assembly ?? Assembly.GetEntryAssembly())?.GetCustomAttributes(typeof(ReleaseDateAttribute), false);
- return attribute?.Length >= 1 ? ((ReleaseDateAttribute)attribute[0]).ReleaseDate : null;
+ return (assembly ?? Assembly.GetEntryAssembly())?.GetCustomAttribute()?.ReleaseDate;
}
}
diff --git a/IntelliTect.Multitool/RepositoryPaths.cs b/IntelliTect.Multitool/RepositoryPaths.cs
index bb57510..35db6a5 100644
--- a/IntelliTect.Multitool/RepositoryPaths.cs
+++ b/IntelliTect.Multitool/RepositoryPaths.cs
@@ -43,7 +43,7 @@ public static string GetDefaultRepoRoot()
}
}
// Search from the project directory if we are live unit testing or if the initial search failed.
- if (BuildVariables.TryGetValue("ProjectPath", out string? projectPath))
+ if (BuildVariables.TryGetValue("ProjectPath", out string? projectPath) && !string.IsNullOrWhiteSpace(projectPath))
{
searchStartDirectory = new FileInfo(projectPath).Directory;
if (TrySearchForGitContainingDirectory(searchStartDirectory, out gitDirectory)