diff --git a/src/ModularPipelines.Build/ModularPipelines.Build.csproj b/src/ModularPipelines.Build/ModularPipelines.Build.csproj
index a329e88bda..d9cb5f2ee7 100644
--- a/src/ModularPipelines.Build/ModularPipelines.Build.csproj
+++ b/src/ModularPipelines.Build/ModularPipelines.Build.csproj
@@ -24,6 +24,7 @@
+
PreserveNewest
diff --git a/src/ModularPipelines.SourceGenerator/BuildMethodGenerator.cs b/src/ModularPipelines.SourceGenerator/BuildMethodGenerator.cs
index 69e1f92bc0..c6df587dc7 100644
--- a/src/ModularPipelines.SourceGenerator/BuildMethodGenerator.cs
+++ b/src/ModularPipelines.SourceGenerator/BuildMethodGenerator.cs
@@ -4,8 +4,18 @@
namespace ModularPipelines.SourceGenerator;
///
-/// Generates the Build() method for options classes.
+/// Generates the BuildCommandLine() method for options classes.
///
+///
+/// The method is named BuildCommandLine() instead of Build() to avoid conflicts with
+/// options classes that have properties named "Build" (e.g., AptGetOptions has a Build
+/// property for the --build flag). In C#, a property and parameterless method cannot
+/// share the same name.
+///
+/// This method is internal infrastructure called by ICommandLineBuilder - users don't
+/// call it directly on options instances. They pass options to command methods like
+/// context.Git().Checkout(options).
+///
internal static class BuildMethodGenerator
{
///
@@ -19,7 +29,7 @@ internal static class BuildMethodGenerator
private const string GeneratorVersion = "1.0.0";
///
- /// Generates the source code for the Build() method.
+ /// Generates the source code for the BuildCommandLine() method.
///
/// The options class information.
/// The generated source code.
@@ -46,7 +56,7 @@ public static string Generate(OptionsClassInfo info)
sb.AppendLine(" /// ");
sb.AppendLine(" /// Builds the command line arguments from this options instance.");
sb.AppendLine(" /// ");
- sb.AppendLine(" public ModularPipelines.Models.CommandLine Build()");
+ sb.AppendLine(" public ModularPipelines.Models.CommandLine BuildCommandLine()");
sb.AppendLine(" {");
sb.AppendLine(" var args = new System.Collections.Generic.List();");
diff --git a/src/ModularPipelines.SourceGenerator/CommandOptionsGenerator.cs b/src/ModularPipelines.SourceGenerator/CommandOptionsGenerator.cs
index d327a155f3..8ea7fa02c1 100644
--- a/src/ModularPipelines.SourceGenerator/CommandOptionsGenerator.cs
+++ b/src/ModularPipelines.SourceGenerator/CommandOptionsGenerator.cs
@@ -8,7 +8,7 @@ namespace ModularPipelines.SourceGenerator;
///
/// Source generator that validates CommandLineToolOptions classes
-/// and generates optimized Build() methods.
+/// and generates optimized BuildCommandLine() methods.
///
[Generator]
public sealed class CommandOptionsGenerator : IIncrementalGenerator
@@ -237,7 +237,7 @@ private static void GenerateCode(SourceProductionContext context, OptionsClassIn
propNames, group.Key));
}
- // Generate Build() method
+ // Generate BuildCommandLine() method
var source = BuildMethodGenerator.Generate(info);
context.AddSource($"{info.ClassName}.g.cs", source);
}
diff --git a/src/ModularPipelines.SourceGenerator/ModuleClassInfo.cs b/src/ModularPipelines.SourceGenerator/ModuleClassInfo.cs
new file mode 100644
index 0000000000..8284c374a1
--- /dev/null
+++ b/src/ModularPipelines.SourceGenerator/ModuleClassInfo.cs
@@ -0,0 +1,14 @@
+using Microsoft.CodeAnalysis;
+
+namespace ModularPipelines.SourceGenerator;
+
+///
+/// Information about a Module class discovered by the generator.
+///
+internal sealed record ModuleClassInfo(
+ string Namespace,
+ string ClassName,
+ string ResultTypeName,
+ string ResultTypeFullName,
+ Location Location
+);
diff --git a/src/ModularPipelines.SourceGenerator/ModuleExtensionsGenerator.cs b/src/ModularPipelines.SourceGenerator/ModuleExtensionsGenerator.cs
new file mode 100644
index 0000000000..fbee419fd6
--- /dev/null
+++ b/src/ModularPipelines.SourceGenerator/ModuleExtensionsGenerator.cs
@@ -0,0 +1,233 @@
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace ModularPipelines.SourceGenerator;
+
+///
+/// Source generator that creates type-safe GetModule extension methods for Module classes.
+/// For each class that inherits from Module<T>, generates:
+/// - GetXxxModuleResult(this IModuleContext context) extension method (strips "Module" suffix)
+/// - GetXxxModuleResultIfRegistered(this IModuleContext context) extension method
+///
+[Generator]
+public sealed class ModuleExtensionsGenerator : IIncrementalGenerator
+{
+ ///
+ /// The fully qualified name of the Module<T> base class.
+ ///
+ internal const string ModuleBaseFullName = "ModularPipelines.Modules.Module`1";
+
+ private const string GeneratorName = "ModularPipelines.SourceGenerator";
+ private const string GeneratorVersion = "1.0.0";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Create syntax provider that finds class declarations with base types
+ var moduleClasses = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ predicate: static (node, _) => IsCandidate(node),
+ transform: static (ctx, _) => GetModuleClassInfo(ctx))
+ .Where(static info => info is not null)
+ .Select(static (info, _) => info!);
+
+ // Collect all modules and generate a single extensions file
+ var collectedModules = moduleClasses.Collect();
+ context.RegisterSourceOutput(collectedModules, static (ctx, modules) => GenerateExtensions(ctx, modules));
+ }
+
+ ///
+ /// Checks if a syntax node is a potential candidate for module discovery.
+ /// Returns true for class declarations with base types.
+ ///
+ private static bool IsCandidate(SyntaxNode node)
+ {
+ return node is ClassDeclarationSyntax classDeclaration &&
+ classDeclaration.BaseList != null &&
+ classDeclaration.BaseList.Types.Count > 0;
+ }
+
+ ///
+ /// Extracts ModuleClassInfo from a type declaration if it inherits from Module<T>.
+ ///
+ private static ModuleClassInfo? GetModuleClassInfo(GeneratorSyntaxContext context)
+ {
+ var classDeclaration = (ClassDeclarationSyntax)context.Node;
+ var semanticModel = context.SemanticModel;
+
+ // Get the declared symbol for this type
+ if (semanticModel.GetDeclaredSymbol(classDeclaration) is not INamedTypeSymbol typeSymbol)
+ {
+ return null;
+ }
+
+ // Skip abstract classes - they can't be instantiated as modules
+ if (typeSymbol.IsAbstract)
+ {
+ return null;
+ }
+
+ // Check if this type inherits from Module and get the result type
+ var resultType = GetModuleResultType(typeSymbol, semanticModel.Compilation);
+ if (resultType is null)
+ {
+ return null;
+ }
+
+ // Extract namespace
+ var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
+ ? string.Empty
+ : typeSymbol.ContainingNamespace.ToDisplayString();
+
+ // Get type names for code generation
+ var resultTypeName = resultType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
+ var resultTypeFullName = resultType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+
+ return new ModuleClassInfo(
+ Namespace: namespaceName,
+ ClassName: typeSymbol.Name,
+ ResultTypeName: resultTypeName,
+ ResultTypeFullName: resultTypeFullName,
+ Location: classDeclaration.Identifier.GetLocation()
+ );
+ }
+
+ ///
+ /// Gets the result type T from Module<T> if the type inherits from it.
+ ///
+ private static ITypeSymbol? GetModuleResultType(INamedTypeSymbol type, Compilation compilation)
+ {
+ var moduleBaseType = compilation.GetTypeByMetadataName(ModuleBaseFullName);
+ if (moduleBaseType is null)
+ {
+ return null;
+ }
+
+ var current = type.BaseType;
+ while (current is not null)
+ {
+ if (current.IsGenericType &&
+ SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, moduleBaseType))
+ {
+ return current.TypeArguments[0];
+ }
+
+ current = current.BaseType;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Generates the extension methods file containing all module accessors.
+ ///
+ private static void GenerateExtensions(SourceProductionContext context, ImmutableArray modules)
+ {
+ if (modules.IsEmpty)
+ {
+ return;
+ }
+
+ var sb = new StringBuilder();
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine();
+ sb.AppendLine("using System.CodeDom.Compiler;");
+ sb.AppendLine("using ModularPipelines.Context;");
+ sb.AppendLine("using ModularPipelines.Models;");
+ sb.AppendLine("using ModularPipelines.Modules;");
+ sb.AppendLine();
+
+ // Add using statements for module namespaces
+ var distinctNamespaces = modules
+ .Select(m => m.Namespace)
+ .Where(ns => !string.IsNullOrEmpty(ns))
+ .Distinct()
+ .OrderBy(ns => ns);
+
+ foreach (var ns in distinctNamespaces)
+ {
+ sb.AppendLine($"using {ns};");
+ }
+
+ sb.AppendLine();
+ sb.AppendLine("namespace ModularPipelines.Generated;");
+ sb.AppendLine();
+ sb.AppendLine("/// ");
+ sb.AppendLine("/// Type-safe extension methods for retrieving module results.");
+ sb.AppendLine("/// ");
+ sb.AppendLine($"[GeneratedCode(\"{GeneratorName}\", \"{GeneratorVersion}\")]");
+ sb.AppendLine("public static class ModuleContextExtensions");
+ sb.AppendLine("{");
+
+ // Group modules by class name to handle potential duplicates
+ var modulesByName = modules.GroupBy(m => m.ClassName).ToList();
+
+ foreach (var moduleGroup in modulesByName)
+ {
+ var module = moduleGroup.First();
+ var methodName = StripModuleSuffix(module.ClassName);
+
+ // GetXxxModuleResult method - returns non-nullable (e.g., GetBuildModuleResult for BuildModule)
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Gets the result of .");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// The module context.");
+ sb.AppendLine($" /// The module result.");
+ sb.AppendLine($" /// Thrown when the module is not registered.");
+ sb.AppendLine($" public static ModuleResult<{module.ResultTypeFullName}> Get{methodName}ModuleResult(this IModuleContext context)");
+ sb.AppendLine($" => context.GetModule<{module.ClassName}, {module.ResultTypeFullName}>();");
+ sb.AppendLine();
+
+ // GetXxxModuleResultIfRegistered method - returns nullable
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// Gets the result of if it is registered, otherwise null.");
+ sb.AppendLine($" /// ");
+ sb.AppendLine($" /// The module context.");
+ sb.AppendLine($" /// The module result, or null if not registered.");
+ sb.AppendLine($" public static ModuleResult<{module.ResultTypeFullName}>? Get{methodName}ModuleResultIfRegistered(this IModuleContext context)");
+ sb.AppendLine($" => context.GetModuleIfRegistered<{module.ClassName}, {module.ResultTypeFullName}>();");
+ sb.AppendLine();
+ }
+
+ sb.AppendLine("}");
+
+ context.AddSource("ModuleContextExtensions.g.cs", sb.ToString());
+ }
+
+ ///
+ /// Escapes a string for use in XML documentation comments.
+ ///
+ private static string EscapeXmlComment(string text)
+ {
+ return text.Replace("&", "&").Replace("<", "<").Replace(">", ">");
+ }
+
+ ///
+ /// Strips the "Module" suffix from a class name to create a cleaner method name.
+ ///
+ ///
+ /// Examples:
+ /// - "BuildModule" → "Build" (generates GetBuildModuleResult)
+ /// - "DeployToProduction" → "DeployToProduction" (generates GetDeployToProductionModuleResult)
+ /// - "Module" → "Module" (edge case: keeps name to avoid empty string)
+ ///
+ /// The length check (className.Length > suffix.Length) ensures that a class named
+ /// exactly "Module" won't be stripped to an empty string.
+ ///
+ private static string StripModuleSuffix(string className)
+ {
+ const string suffix = "Module";
+
+ // Only strip if there's actual content before "Module" (prevents "Module" → "")
+ if (className.Length > suffix.Length && className.EndsWith(suffix))
+ {
+ return className.Substring(0, className.Length - suffix.Length);
+ }
+
+ return className;
+ }
+}
diff --git a/src/ModularPipelines/ModularPipelines.csproj b/src/ModularPipelines/ModularPipelines.csproj
index 0dca77833f..f895cac18d 100644
--- a/src/ModularPipelines/ModularPipelines.csproj
+++ b/src/ModularPipelines/ModularPipelines.csproj
@@ -78,5 +78,15 @@
+
+
+
+ $(TargetsForTfmSpecificContentInPackage);_AddSourceGeneratorToPackage
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/BashCommandOptions.cs b/src/ModularPipelines/Options/BashCommandOptions.cs
index be2ec33cb9..19adf3eb12 100644
--- a/src/ModularPipelines/Options/BashCommandOptions.cs
+++ b/src/ModularPipelines/Options/BashCommandOptions.cs
@@ -8,4 +8,4 @@ namespace ModularPipelines.Options;
///
/// The bash command to execute.
[ExcludeFromCodeCoverage]
-public record BashCommandOptions([property: CliOption("-c")] string Command) : BashOptions;
\ No newline at end of file
+public partial record BashCommandOptions([property: CliOption("-c")] string Command) : BashOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/BashFileOptions.cs b/src/ModularPipelines/Options/BashFileOptions.cs
index 0ccc425dc0..e8a4d2a884 100644
--- a/src/ModularPipelines/Options/BashFileOptions.cs
+++ b/src/ModularPipelines/Options/BashFileOptions.cs
@@ -4,4 +4,4 @@
namespace ModularPipelines.Options;
[ExcludeFromCodeCoverage]
-public record BashFileOptions([property: CliArgument(Placement = ArgumentPlacement.BeforeOptions)] string FilePath) : BashOptions;
\ No newline at end of file
+public partial record BashFileOptions([property: CliArgument(Placement = ArgumentPlacement.BeforeOptions)] string FilePath) : BashOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/BashOptions.cs b/src/ModularPipelines/Options/BashOptions.cs
index 5b3e38fe2b..5895bc0b9b 100644
--- a/src/ModularPipelines/Options/BashOptions.cs
+++ b/src/ModularPipelines/Options/BashOptions.cs
@@ -8,4 +8,4 @@ namespace ModularPipelines.Options;
///
[ExcludeFromCodeCoverage]
[CliTool("bash")]
-public record BashOptions : CommandLineToolOptions;
\ No newline at end of file
+public partial record BashOptions : CommandLineToolOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetAutocleanOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetAutocleanOptions.cs
index f4720cad19..b6acac965b 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetAutocleanOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetAutocleanOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetAutocleanOptions : AptGetOptions
+public partial record AptGetAutocleanOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "autoclean";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetBuildDepOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetBuildDepOptions.cs
index d3a1af6c9e..92b81580c9 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetBuildDepOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetBuildDepOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetBuildDepOptions : AptGetOptions
+public partial record AptGetBuildDepOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "build-dep";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetCheckOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetCheckOptions.cs
index 7585117bab..9d2dc6ca7c 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetCheckOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetCheckOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetCheckOptions : AptGetOptions
+public partial record AptGetCheckOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "check";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetCleanOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetCleanOptions.cs
index fbba5ac224..a6e640f291 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetCleanOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetCleanOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetCleanOptions : AptGetOptions
+public partial record AptGetCleanOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "clean";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetDistUpgradeOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetDistUpgradeOptions.cs
index b5b62ee94e..66527c50ce 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetDistUpgradeOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetDistUpgradeOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetDistUpgradeOptions : AptGetOptions
+public partial record AptGetDistUpgradeOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "dist-upgrade";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetInstallOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetInstallOptions.cs
index d312654e1e..6c91edaafa 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetInstallOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetInstallOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetInstallOptions : AptGetOptions
+public partial record AptGetInstallOptions : AptGetOptions
{
///
/// Initializes a new instance of the class
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetOptions.cs
index bce98187b9..ddf01de239 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetOptions.cs
@@ -5,7 +5,7 @@ namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
[CliTool("apt-get")]
-public record AptGetOptions : CommandLineToolOptions
+public partial record AptGetOptions : CommandLineToolOptions
{
[CliFlag("--download-only")]
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetPackageOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetPackageOptions.cs
index 8ca48cad96..92b821cc52 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetPackageOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetPackageOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetPackageOptions : AptGetOptions
+public partial record AptGetPackageOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "package";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetRemoveOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetRemoveOptions.cs
index 7557bbda38..64ad29fd22 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetRemoveOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetRemoveOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetRemoveOptions : AptGetOptions
+public partial record AptGetRemoveOptions : AptGetOptions
{
public AptGetRemoveOptions(string package)
{
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetSourceOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetSourceOptions.cs
index d1688e1bd0..c7d273e99b 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetSourceOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetSourceOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetSourceOptions : AptGetOptions
+public partial record AptGetSourceOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "source";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetUpdateOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetUpdateOptions.cs
index 21f5b07229..bc8a30f988 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetUpdateOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetUpdateOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetUpdateOptions : AptGetOptions
+public partial record AptGetUpdateOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "update";
diff --git a/src/ModularPipelines/Options/Linux/AptGet/AptGetUpgradeOptions.cs b/src/ModularPipelines/Options/Linux/AptGet/AptGetUpgradeOptions.cs
index d90aa83573..c13d94f64e 100644
--- a/src/ModularPipelines/Options/Linux/AptGet/AptGetUpgradeOptions.cs
+++ b/src/ModularPipelines/Options/Linux/AptGet/AptGetUpgradeOptions.cs
@@ -4,7 +4,7 @@
namespace ModularPipelines.Options.Linux.AptGet;
[ExcludeFromCodeCoverage]
-public record AptGetUpgradeOptions : AptGetOptions
+public partial record AptGetUpgradeOptions : AptGetOptions
{
[CliArgument(Placement = ArgumentPlacement.AfterOptions)]
public virtual string CommandName { get; } = "upgrade";
diff --git a/src/ModularPipelines/Options/Linux/DpkgInstallOptions.cs b/src/ModularPipelines/Options/Linux/DpkgInstallOptions.cs
index ae9739e4de..2950f6d5ea 100644
--- a/src/ModularPipelines/Options/Linux/DpkgInstallOptions.cs
+++ b/src/ModularPipelines/Options/Linux/DpkgInstallOptions.cs
@@ -5,7 +5,7 @@ namespace ModularPipelines.Options.Linux;
[ExcludeFromCodeCoverage]
[CliTool("dpkg")]
-public record DpkgInstallOptions : CommandLineToolOptions
+public partial record DpkgInstallOptions : CommandLineToolOptions
{
public DpkgInstallOptions(string path)
{
diff --git a/src/ModularPipelines/Options/Mac/MacBrewOptions.cs b/src/ModularPipelines/Options/Mac/MacBrewOptions.cs
index 618cdaac74..5a896a4880 100644
--- a/src/ModularPipelines/Options/Mac/MacBrewOptions.cs
+++ b/src/ModularPipelines/Options/Mac/MacBrewOptions.cs
@@ -5,4 +5,4 @@ namespace ModularPipelines.Options.Mac;
[ExcludeFromCodeCoverage]
[CliTool("brew")]
-public record MacBrewOptions([property: CliOption("--cask")] string PackageName) : CommandLineToolOptions;
\ No newline at end of file
+public partial record MacBrewOptions([property: CliOption("--cask")] string PackageName) : CommandLineToolOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/PowershellFileOptions.cs b/src/ModularPipelines/Options/PowershellFileOptions.cs
index fd0ec7fd57..e1adec92be 100644
--- a/src/ModularPipelines/Options/PowershellFileOptions.cs
+++ b/src/ModularPipelines/Options/PowershellFileOptions.cs
@@ -8,4 +8,4 @@ namespace ModularPipelines.Options;
///
/// The path to the PowerShell script file to execute.
[ExcludeFromCodeCoverage]
-public record PowershellFileOptions([property: CliOption("-File")] string FilePath) : PowershellOptions;
\ No newline at end of file
+public partial record PowershellFileOptions([property: CliOption("-File")] string FilePath) : PowershellOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/PowershellOptions.cs b/src/ModularPipelines/Options/PowershellOptions.cs
index e744885e61..f82aee01e8 100644
--- a/src/ModularPipelines/Options/PowershellOptions.cs
+++ b/src/ModularPipelines/Options/PowershellOptions.cs
@@ -6,4 +6,4 @@ namespace ModularPipelines.Options;
/// Options for executing PowerShell commands using the pwsh executable.
///
[CliTool("pwsh")]
-public record PowershellOptions : CommandLineToolOptions;
\ No newline at end of file
+public partial record PowershellOptions : CommandLineToolOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/PowershellScriptOptions.cs b/src/ModularPipelines/Options/PowershellScriptOptions.cs
index 6439221867..b5986dc79a 100644
--- a/src/ModularPipelines/Options/PowershellScriptOptions.cs
+++ b/src/ModularPipelines/Options/PowershellScriptOptions.cs
@@ -2,4 +2,4 @@
namespace ModularPipelines.Options;
-public record PowershellScriptOptions([property: CliOption("-Command")] string Script) : PowershellOptions;
\ No newline at end of file
+public partial record PowershellScriptOptions([property: CliOption("-Command")] string Script) : PowershellOptions;
\ No newline at end of file
diff --git a/src/ModularPipelines/Options/Windows/MsiInstallerOptions.cs b/src/ModularPipelines/Options/Windows/MsiInstallerOptions.cs
index 4fbd6f7dd2..f78795a5cf 100644
--- a/src/ModularPipelines/Options/Windows/MsiInstallerOptions.cs
+++ b/src/ModularPipelines/Options/Windows/MsiInstallerOptions.cs
@@ -8,4 +8,4 @@ namespace ModularPipelines.Options.Windows;
///
[ExcludeFromCodeCoverage]
[CliTool("msiexec.exe")]
-public record MsiInstallerOptions([property: CliOption("/package")] string MsiPath) : WindowsInstallerOptionsBase;
+public partial record MsiInstallerOptions([property: CliOption("/package")] string MsiPath) : WindowsInstallerOptionsBase;