diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 5d2300a786..a642a02623 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -35,12 +35,7 @@ dotnet build ModularPipelines.Analyzers.sln -c Release
dotnet build ModularPipelines.sln -c Release
```
-3. **Build Examples** (80 seconds):
-```bash
-dotnet build ModularPipelines.Examples.sln -c Release
-```
-
-4. **Run Build Pipeline** (150+ seconds - NEVER CANCEL):
+3. **Run Build Pipeline** (150+ seconds - NEVER CANCEL):
```bash
cd src/ModularPipelines.Build
dotnet run -c Release --framework net8.0
@@ -164,8 +159,6 @@ yarn start
- `src/ModularPipelines.Build/Modules/` - Individual build steps
- `src/ModularPipelines.Build/Program.cs` - Pipeline configuration
-**Examples:**
-- `src/ModularPipelines.Examples/` - Usage examples
## Troubleshooting
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 5ab6cfc0e9..6cdb5bc07c 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -19,7 +19,7 @@ on:
default: true
env:
- SOLUTIONS: ModularPipelines.sln ModularPipelines.Examples.sln src/ModularPipelines.Azure/ModularPipelines.Azure.sln src/ModularPipelines.AmazonWebServices/ModularPipelines.AmazonWebServices.sln src/ModularPipelines.Google/ModularPipelines.Google.sln
+ SOLUTIONS: ModularPipelines.sln src/ModularPipelines.Azure/ModularPipelines.Azure.sln src/ModularPipelines.AmazonWebServices/ModularPipelines.AmazonWebServices.sln src/ModularPipelines.Google/ModularPipelines.Google.sln
jobs:
pipeline:
diff --git a/.gitignore b/.gitignore
index 361d9006d5..991cdaf044 100644
--- a/.gitignore
+++ b/.gitignore
@@ -398,4 +398,6 @@ FodyWeavers.xsd
*.sln.iml
requirements
-.claude/settings.local.json
\ No newline at end of file
+.claude/settings.local.json
+
+.idea/
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Analyzers/.idea/.gitignore b/.idea/.idea.ModularPipelines.Analyzers/.idea/.gitignore
deleted file mode 100644
index f80a3382df..0000000000
--- a/.idea/.idea.ModularPipelines.Analyzers/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/projectSettingsUpdater.xml
-/contentModel.xml
-/modules.xml
-/.idea.ModularPipelines.Analyzers.iml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.ModularPipelines.Analyzers/.idea/.name b/.idea/.idea.ModularPipelines.Analyzers/.idea/.name
deleted file mode 100644
index be0c38245a..0000000000
--- a/.idea/.idea.ModularPipelines.Analyzers/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-ModularPipelines.Analyzers
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Analyzers/.idea/encodings.xml b/.idea/.idea.ModularPipelines.Analyzers/.idea/encodings.xml
deleted file mode 100644
index df87cf951f..0000000000
--- a/.idea/.idea.ModularPipelines.Analyzers/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Analyzers/.idea/indexLayout.xml b/.idea/.idea.ModularPipelines.Analyzers/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163ceb..0000000000
--- a/.idea/.idea.ModularPipelines.Analyzers/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Analyzers/.idea/vcs.xml b/.idea/.idea.ModularPipelines.Analyzers/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddfbb..0000000000
--- a/.idea/.idea.ModularPipelines.Analyzers/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Examples/.idea/.gitignore b/.idea/.idea.ModularPipelines.Examples/.idea/.gitignore
deleted file mode 100644
index 572e923c32..0000000000
--- a/.idea/.idea.ModularPipelines.Examples/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/.idea.ModularPipelines.Examples.iml
-/projectSettingsUpdater.xml
-/contentModel.xml
-/modules.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.ModularPipelines.Examples/.idea/.name b/.idea/.idea.ModularPipelines.Examples/.idea/.name
deleted file mode 100644
index 9e95a81785..0000000000
--- a/.idea/.idea.ModularPipelines.Examples/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-ModularPipelines.Examples
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Examples/.idea/encodings.xml b/.idea/.idea.ModularPipelines.Examples/.idea/encodings.xml
deleted file mode 100644
index df87cf951f..0000000000
--- a/.idea/.idea.ModularPipelines.Examples/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Examples/.idea/indexLayout.xml b/.idea/.idea.ModularPipelines.Examples/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163ceb..0000000000
--- a/.idea/.idea.ModularPipelines.Examples/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines.Examples/.idea/vcs.xml b/.idea/.idea.ModularPipelines.Examples/.idea/vcs.xml
deleted file mode 100644
index 35eb1ddfbb..0000000000
--- a/.idea/.idea.ModularPipelines.Examples/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines/.idea/.gitignore b/.idea/.idea.ModularPipelines/.idea/.gitignore
deleted file mode 100644
index 9cb2e95388..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/contentModel.xml
-/.idea.Pipeline.NET.iml
-/modules.xml
-/projectSettingsUpdater.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.ModularPipelines/.idea/aws.xml b/.idea/.idea.ModularPipelines/.idea/aws.xml
deleted file mode 100644
index 9b821f8cfd..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/aws.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines/.idea/encodings.xml b/.idea/.idea.ModularPipelines/.idea/encodings.xml
deleted file mode 100644
index df87cf951f..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/encodings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines/.idea/indexLayout.xml b/.idea/.idea.ModularPipelines/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163ceb..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines/.idea/material_theme_project_new.xml b/.idea/.idea.ModularPipelines/.idea/material_theme_project_new.xml
deleted file mode 100644
index a4167976b0..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/material_theme_project_new.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.ModularPipelines/.idea/vcs.xml b/.idea/.idea.ModularPipelines/.idea/vcs.xml
deleted file mode 100644
index 94a25f7f4c..0000000000
--- a/.idea/.idea.ModularPipelines/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Pipeline.NET/.idea/aws.xml b/.idea/.idea.Pipeline.NET/.idea/aws.xml
deleted file mode 100644
index 9b821f8cfd..0000000000
--- a/.idea/.idea.Pipeline.NET/.idea/aws.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Pipeline.NET/.idea/indexLayout.xml b/.idea/.idea.Pipeline.NET/.idea/indexLayout.xml
deleted file mode 100644
index 7b08163ceb..0000000000
--- a/.idea/.idea.Pipeline.NET/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Pipeline.NET/.idea/projectSettingsUpdater.xml b/.idea/.idea.Pipeline.NET/.idea/projectSettingsUpdater.xml
deleted file mode 100644
index 4bb9f4d2a0..0000000000
--- a/.idea/.idea.Pipeline.NET/.idea/projectSettingsUpdater.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.Pipeline.NET/.idea/workspace.xml b/.idea/.idea.Pipeline.NET/.idea/workspace.xml
deleted file mode 100644
index 8920cea82e..0000000000
--- a/.idea/.idea.Pipeline.NET/.idea/workspace.xml
+++ /dev/null
@@ -1,143 +0,0 @@
-
-
-
- Pipeline.NET.Build/Pipeline.NET.Build.csproj
- Pipeline.NET.Examples/Pipeline.NET.Examples.csproj
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- "keyToString": {
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "WebServerToolWindowFactoryState": "false",
- "git-widget-placeholder": "feature/initial-work",
- "node.js.detected.package.eslint": "true",
- "node.js.detected.package.tslint": "true",
- "node.js.selected.package.eslint": "(autodetect)",
- "node.js.selected.package.tslint": "(autodetect)",
- "nodejs_package_manager_path": "npm",
- "settings.editor.selected.configurable": "editor.preferences.jsOptions",
- "vue.rearranger.settings.migration": "true"
- },
- "keyToStringList": {
- "rider.external.source.directories": [
- "C:\\Users\\tom.longhurst\\AppData\\Roaming\\JetBrains\\Rider2023.1\\resharper-host\\DecompilerCache",
- "C:\\Users\\tom.longhurst\\AppData\\Roaming\\JetBrains\\Rider2023.1\\resharper-host\\SourcesCache",
- "C:\\Users\\tom.longhurst\\AppData\\Local\\Symbols\\src"
- ]
- }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1684830433835
-
-
- 1684830433835
-
-
-
-
-
-
- 1685277157807
-
-
-
- 1685277157807
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- file://$PROJECT_DIR$/Pipeline.NET/Host/PipelineHostBuilder.cs
- 100
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
index 43f8153b57..2cde2071bb 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -9,8 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
# Build the entire solution
dotnet build ModularPipelines.sln -c Release
-# Build specific solution (examples, analyzers, etc.)
-dotnet build ModularPipelines.Examples.sln -c Release
+# Build specific solution (analyzers, etc.)
dotnet build ModularPipelines.Analyzers.sln -c Release
# Run the build pipeline (from src/ModularPipelines.Build)
diff --git a/ModularPipelines.Examples.sln b/ModularPipelines.Examples.sln
deleted file mode 100644
index aa2a7d21ea..0000000000
--- a/ModularPipelines.Examples.sln
+++ /dev/null
@@ -1,62 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines", "src\ModularPipelines\ModularPipelines.csproj", "{67A40317-7A69-43A3-B71F-7F0D6298EB01}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Examples", "src\ModularPipelines.Examples\ModularPipelines.Examples.csproj", "{01A8D780-3D35-4BEF-A6E5-DEDBB3B44B41}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Azure", "src\ModularPipelines.Azure\ModularPipelines.Azure.csproj", "{7AB8AA64-9408-4FB4-865E-B888CEAF09B9}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Git", "src\ModularPipelines.Git\ModularPipelines.Git.csproj", "{AE342AE7-F3A0-4BDE-857D-27EA710A3084}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.DotNet", "src\ModularPipelines.DotNet\ModularPipelines.DotNet.csproj", "{80B2A133-2188-4CD4-9DEA-4D6B8C15F1DD}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Analyzers", "src\ModularPipelines.Analyzers\ModularPipelines.Analyzers\ModularPipelines.Analyzers.csproj", "{FA16350A-6C60-4AA1-8BCB-8198338FCBE2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Analyzers.Package", "src\ModularPipelines.Analyzers\ModularPipelines.Analyzers.Package\ModularPipelines.Analyzers.Package.csproj", "{A561E991-A4E9-4CDE-80A8-F64B3F308A82}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModularPipelines.Analyzers.CodeFixes", "src\ModularPipelines.Analyzers\ModularPipelines.Analyzers.CodeFixes\ModularPipelines.Analyzers.CodeFixes.csproj", "{28F1F43A-512D-4A77-825C-D1C20856E161}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {67A40317-7A69-43A3-B71F-7F0D6298EB01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {67A40317-7A69-43A3-B71F-7F0D6298EB01}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {67A40317-7A69-43A3-B71F-7F0D6298EB01}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {67A40317-7A69-43A3-B71F-7F0D6298EB01}.Release|Any CPU.Build.0 = Release|Any CPU
- {01A8D780-3D35-4BEF-A6E5-DEDBB3B44B41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {01A8D780-3D35-4BEF-A6E5-DEDBB3B44B41}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {01A8D780-3D35-4BEF-A6E5-DEDBB3B44B41}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {01A8D780-3D35-4BEF-A6E5-DEDBB3B44B41}.Release|Any CPU.Build.0 = Release|Any CPU
- {7AB8AA64-9408-4FB4-865E-B888CEAF09B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7AB8AA64-9408-4FB4-865E-B888CEAF09B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7AB8AA64-9408-4FB4-865E-B888CEAF09B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7AB8AA64-9408-4FB4-865E-B888CEAF09B9}.Release|Any CPU.Build.0 = Release|Any CPU
- {AE342AE7-F3A0-4BDE-857D-27EA710A3084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AE342AE7-F3A0-4BDE-857D-27EA710A3084}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AE342AE7-F3A0-4BDE-857D-27EA710A3084}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AE342AE7-F3A0-4BDE-857D-27EA710A3084}.Release|Any CPU.Build.0 = Release|Any CPU
- {80B2A133-2188-4CD4-9DEA-4D6B8C15F1DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {80B2A133-2188-4CD4-9DEA-4D6B8C15F1DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {80B2A133-2188-4CD4-9DEA-4D6B8C15F1DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {80B2A133-2188-4CD4-9DEA-4D6B8C15F1DD}.Release|Any CPU.Build.0 = Release|Any CPU
- {DE55E147-8CC6-4B0C-BB07-A4C464A40DED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DE55E147-8CC6-4B0C-BB07-A4C464A40DED}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DE55E147-8CC6-4B0C-BB07-A4C464A40DED}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DE55E147-8CC6-4B0C-BB07-A4C464A40DED}.Release|Any CPU.Build.0 = Release|Any CPU
- {FA16350A-6C60-4AA1-8BCB-8198338FCBE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FA16350A-6C60-4AA1-8BCB-8198338FCBE2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FA16350A-6C60-4AA1-8BCB-8198338FCBE2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FA16350A-6C60-4AA1-8BCB-8198338FCBE2}.Release|Any CPU.Build.0 = Release|Any CPU
- {A561E991-A4E9-4CDE-80A8-F64B3F308A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A561E991-A4E9-4CDE-80A8-F64B3F308A82}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A561E991-A4E9-4CDE-80A8-F64B3F308A82}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A561E991-A4E9-4CDE-80A8-F64B3F308A82}.Release|Any CPU.Build.0 = Release|Any CPU
- {28F1F43A-512D-4A77-825C-D1C20856E161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {28F1F43A-512D-4A77-825C-D1C20856E161}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {28F1F43A-512D-4A77-825C-D1C20856E161}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {28F1F43A-512D-4A77-825C-D1C20856E161}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
-EndGlobal
diff --git a/batch_fix.py b/batch_fix.py
new file mode 100644
index 0000000000..4ff12be4e9
--- /dev/null
+++ b/batch_fix.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+"""
+Batch fix remaining migration issues
+"""
+import re
+from pathlib import Path
+
+files_to_fix = [
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\FailedPipelineTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\ModuleHistoryTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\ResultsRepositoryTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\SkipDependabotAttributeTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\IgnoredFailureTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\RetryTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\SkippedModuleTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\DependsOnAllInheritingFromTests.cs'),
+]
+
+for file_path in files_to_fix:
+ if not file_path.exists():
+ print(f"SKIP: {file_path}")
+ continue
+
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ original = content
+
+ # Fix protected override to public override
+ content = re.sub(
+ r'protected override (async )?Task<',
+ r'public override \1Task<',
+ content
+ )
+
+ # Fix protected internal override to public
+ content = re.sub(
+ r'protected internal override (async )?Task<',
+ r'public override \1Task<',
+ content
+ )
+
+ # Fix ShouldSkip method
+ content = re.sub(
+ r'protected override Task ShouldSkip\(IPipelineContext context\)',
+ r'public Task ShouldSkipAsync(IPipelineContext context)',
+ content
+ )
+
+ # Add IModuleSkipLogic interface if ShouldSkipAsync exists
+ if 'ShouldSkipAsync' in content and 'IModuleSkipLogic' not in content:
+ content = re.sub(
+ r'(class \w+ : ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleSkipLogic\n {',
+ content
+ )
+
+ # Fix ShouldIgnoreFailures method
+ content = re.sub(
+ r'protected override Task ShouldIgnoreFailures\(IPipelineContext context, Exception exception\)',
+ r'public Task ShouldIgnoreFailuresAsync(IPipelineContext context, Exception exception)',
+ content
+ )
+
+ # Add IModuleErrorHandling interface if ShouldIgnoreFailuresAsync exists
+ if 'ShouldIgnoreFailuresAsync' in content and 'IModuleErrorHandling' not in content:
+ content = re.sub(
+ r'(class \w+ : ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleErrorHandling\n {',
+ content
+ )
+
+ # Fix UseResultFromHistoryIfSkipped
+ content = re.sub(
+ r'protected override Task UseResultFromHistoryIfSkipped\(IPipelineContext context\)',
+ r'public Task UseResultFromHistoryIfSkippedAsync(IPipelineContext context)',
+ content
+ )
+
+ # Fix RetryPolicy property to GetRetryPolicy method
+ content = re.sub(
+ r'protected override (AsyncRetryPolicy<[^>]+>) RetryPolicy \{ get; \} =\s*([^;]+);',
+ r'public IAsyncPolicy GetRetryPolicy() => \2;',
+ content
+ )
+
+ # Add IModuleRetryPolicy interface if GetRetryPolicy exists
+ if 'GetRetryPolicy' in content and 'IModuleRetryPolicy' not in content:
+ content = re.sub(
+ r'(class \w+ : ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleRetryPolicy\n {',
+ content
+ )
+
+ # Add using statement for behaviors if needed
+ if any(x in content for x in ['IModuleSkipLogic', 'IModuleErrorHandling', 'IModuleTimeout', 'IModuleRetryPolicy']):
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ # Find last using statement
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ if content != original:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+ print(f"FIXED: {file_path.name}")
+ else:
+ print(f"OK: {file_path.name}")
+
+print("\nDone!")
diff --git a/docs/features/SUBMODULE_API.md b/docs/features/SUBMODULE_API.md
new file mode 100644
index 0000000000..6f3e41a697
--- /dev/null
+++ b/docs/features/SUBMODULE_API.md
@@ -0,0 +1,617 @@
+# SubModule API Documentation
+
+## Overview
+
+The SubModule API provides a way to create named sub-tasks within your modules. This is particularly useful for:
+
+- Processing items in loops with clear progress indication
+- Parallel processing with individual task identification
+- Structured logging that shows exactly what's being processed
+- Performance monitoring with clear operation boundaries
+
+## Quick Start
+
+```csharp
+public class ProcessFilesModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var files = new[] { "file1.txt", "file2.txt", "file3.txt" };
+
+ return await files.ToAsyncProcessorBuilder()
+ .SelectAsync(file => SubModule(context, file, async () =>
+ {
+ context.Logger.LogInformation("Processing {File}", file);
+ var content = await File.ReadAllTextAsync(file);
+ return content.ToUpper();
+ }, cancellationToken))
+ .ProcessInParallel();
+ }
+}
+```
+
+## API Signatures
+
+The SubModule API provides 4 overloads to cover all scenarios:
+
+### 1. Async with Result
+
+Execute an asynchronous operation that returns a value.
+
+```csharp
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func> action,
+ CancellationToken cancellationToken = default)
+```
+
+**Parameters**:
+- `context`: The pipeline context (provides logging and services)
+- `name`: Descriptive name for the sub-task
+- `action`: Async function to execute
+- `cancellationToken`: Optional cancellation token
+
+**Returns**: Task with the result of the action
+
+**Example**:
+```csharp
+var result = await SubModule(context, "Download File", async () =>
+{
+ var content = await httpClient.GetStringAsync(url);
+ return content;
+}, cancellationToken);
+```
+
+### 2. Sync with Result
+
+Execute a synchronous operation that returns a value.
+
+```csharp
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func action,
+ CancellationToken cancellationToken = default)
+```
+
+**Parameters**:
+- `context`: The pipeline context (provides logging and services)
+- `name`: Descriptive name for the sub-task
+- `action`: Sync function to execute
+- `cancellationToken`: Optional cancellation token
+
+**Returns**: Task with the result of the action
+
+**Example**:
+```csharp
+var result = await SubModule(context, "Calculate Hash", () =>
+{
+ using var sha256 = SHA256.Create();
+ return Convert.ToBase64String(sha256.ComputeHash(data));
+}, cancellationToken);
+```
+
+### 3. Async without Result
+
+Execute an asynchronous operation that doesn't return a value.
+
+```csharp
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func action,
+ CancellationToken cancellationToken = default)
+```
+
+**Parameters**:
+- `context`: The pipeline context (provides logging and services)
+- `name`: Descriptive name for the sub-task
+- `action`: Async function to execute
+- `cancellationToken`: Optional cancellation token
+
+**Returns**: Task representing the async operation
+
+**Example**:
+```csharp
+await SubModule(context, "Upload File", async () =>
+{
+ await ftpClient.UploadFileAsync(localPath, remotePath);
+}, cancellationToken);
+```
+
+### 4. Sync without Result
+
+Execute a synchronous operation that doesn't return a value.
+
+```csharp
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Action action,
+ CancellationToken cancellationToken = default)
+```
+
+**Parameters**:
+- `context`: The pipeline context (provides logging and services)
+- `name`: Descriptive name for the sub-task
+- `action`: Sync action to execute
+- `cancellationToken`: Optional cancellation token
+
+**Returns**: Task representing the operation
+
+**Example**:
+```csharp
+await SubModule(context, "Write Config", () =>
+{
+ File.WriteAllText(configPath, configContent);
+}, cancellationToken);
+```
+
+## Logging
+
+SubModule automatically provides structured logging with the format:
+
+```
+[SubModule] ModuleName.SubModuleName starting
+[SubModule] ModuleName.SubModuleName completed
+```
+
+Or in case of errors:
+
+```
+[SubModule] ModuleName.SubModuleName failed
+```
+
+### Log Levels
+
+- **Starting**: LogDebug
+- **Completed**: LogDebug
+- **Failed**: LogError (includes exception details)
+
+### Example Output
+
+```
+[SubModule] ProcessFilesModule.file1.txt starting
+[SubModule] ProcessFilesModule.file1.txt completed
+[SubModule] ProcessFilesModule.file2.txt starting
+[SubModule] ProcessFilesModule.file2.txt completed
+[SubModule] ProcessFilesModule.file3.txt starting
+[SubModule] ProcessFilesModule.file3.txt completed
+```
+
+## Use Cases
+
+### 1. Parallel File Processing
+
+Process multiple files in parallel with clear progress indication:
+
+```csharp
+public class ProcessImagesModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var imageFiles = context.FileSystem.RootDirectory
+ .GetFolder("images")
+ .FindFiles(x => x.Extension is ".jpg" or ".png");
+
+ return await imageFiles.ToAsyncProcessorBuilder()
+ .SelectAsync(file => SubModule(context, file.Name, async () =>
+ {
+ using var image = await Image.LoadAsync(file.Path);
+
+ // Resize
+ image.Mutate(x => x.Resize(800, 600));
+
+ // Save
+ var outputPath = Path.Combine("output", file.Name);
+ await image.SaveAsync(outputPath);
+
+ return new ImageResult
+ {
+ OriginalPath = file.Path,
+ ProcessedPath = outputPath
+ };
+ }, cancellationToken))
+ .ProcessInParallel(maxDegreeOfParallelism: 4);
+ }
+}
+```
+
+### 2. Sequential Processing with Progress
+
+Process items sequentially with clear progress tracking:
+
+```csharp
+public class DeployServicesModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var services = new[] { "api", "web", "worker" };
+ var results = new List();
+
+ foreach (var service in services)
+ {
+ var result = await SubModule(context, $"Deploy {service}", async () =>
+ {
+ context.Logger.LogInformation("Deploying {Service}...", service);
+
+ // Build Docker image
+ await context.Docker().Build(new DockerBuildOptions
+ {
+ Dockerfile = $"./services/{service}/Dockerfile",
+ Tag = $"{service}:latest"
+ }, cancellationToken);
+
+ // Push to registry
+ await context.Docker().Push($"{service}:latest", cancellationToken);
+
+ // Deploy to Kubernetes
+ await context.Kubectl().Apply($"./k8s/{service}.yaml", cancellationToken);
+
+ return new DeploymentResult
+ {
+ Service = service,
+ Success = true,
+ Timestamp = DateTime.UtcNow
+ };
+ }, cancellationToken);
+
+ results.Add(result);
+ }
+
+ return results.ToArray();
+ }
+}
+```
+
+### 3. Data Transformation Pipeline
+
+Transform data through multiple stages with clear operation boundaries:
+
+```csharp
+public class DataProcessingModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // Stage 1: Extract
+ var rawData = await SubModule(context, "Extract Data", async () =>
+ {
+ return await context.Http().GetAsync("https://api.example.com/data");
+ }, cancellationToken);
+
+ // Stage 2: Transform
+ var transformedData = await SubModule(context, "Transform Data", () =>
+ {
+ return rawData.Select(item => new TransformedData
+ {
+ Id = item.Id,
+ Value = item.RawValue.ToUpper(),
+ Processed = DateTime.UtcNow
+ }).ToList();
+ }, cancellationToken);
+
+ // Stage 3: Load
+ await SubModule(context, "Load Data", async () =>
+ {
+ await database.BulkInsertAsync(transformedData);
+ }, cancellationToken);
+
+ return new ProcessedData
+ {
+ RecordCount = transformedData.Count,
+ ProcessedAt = DateTime.UtcNow
+ };
+ }
+}
+```
+
+### 4. API Batch Operations
+
+Make multiple API calls with clear tracking:
+
+```csharp
+public class SyncUsersModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var userIds = await GetUserIdsToSync();
+
+ return await userIds.ToAsyncProcessorBuilder()
+ .SelectAsync(userId => SubModule(context, $"User {userId}", async () =>
+ {
+ try
+ {
+ var user = await apiClient.GetUserAsync(userId);
+ await database.UpsertUserAsync(user);
+
+ return new UserSyncResult
+ {
+ UserId = userId,
+ Success = true
+ };
+ }
+ catch (Exception ex)
+ {
+ context.Logger.LogError(ex, "Failed to sync user {UserId}", userId);
+
+ return new UserSyncResult
+ {
+ UserId = userId,
+ Success = false,
+ Error = ex.Message
+ };
+ }
+ }, cancellationToken))
+ .ProcessInParallel(maxDegreeOfParallelism: 10);
+ }
+}
+```
+
+## Best Practices
+
+### 1. Use Descriptive Names
+
+Choose names that clearly describe what the sub-task is doing:
+
+```csharp
+// Good
+await SubModule(context, "Download dependencies", ...)
+await SubModule(context, $"Process {file.Name}", ...)
+await SubModule(context, "Validate configuration", ...)
+
+// Bad
+await SubModule(context, "Step 1", ...)
+await SubModule(context, "Do stuff", ...)
+await SubModule(context, "Task", ...)
+```
+
+### 2. Include Context in Names
+
+For loop processing, include the item identifier in the name:
+
+```csharp
+// Good
+await SubModule(context, $"Build {project.Name}", ...)
+await SubModule(context, $"Deploy to {environment}", ...)
+await SubModule(context, $"Test {testSuite.Name}", ...)
+
+// Less Helpful
+await SubModule(context, "Build project", ...)
+await SubModule(context, "Deploy", ...)
+await SubModule(context, "Test", ...)
+```
+
+### 3. Keep SubModules Focused
+
+Each SubModule should represent a single logical operation:
+
+```csharp
+// Good - Separate concerns
+var downloaded = await SubModule(context, "Download", async () =>
+ await DownloadFile(url));
+
+var processed = await SubModule(context, "Process", () =>
+ ProcessData(downloaded));
+
+await SubModule(context, "Upload", async () =>
+ await UploadResult(processed));
+
+// Bad - Too many responsibilities
+await SubModule(context, "Do Everything", async () =>
+{
+ var data = await DownloadFile(url);
+ var processed = ProcessData(data);
+ await UploadResult(processed);
+});
+```
+
+### 4. Use Appropriate Overloads
+
+Choose the right overload for your operation:
+
+```csharp
+// Async with result - when you need async and a return value
+var result = await SubModule(context, "Download", async () =>
+ await DownloadAsync());
+
+// Sync with result - when you have sync code with a return value
+var hash = await SubModule(context, "Hash", () =>
+ ComputeHash(data));
+
+// Async without result - when you need async but no return value
+await SubModule(context, "Upload", async () =>
+ await UploadAsync(data));
+
+// Sync without result - when you have sync code with no return value
+await SubModule(context, "Log", () =>
+ File.AppendAllText(logFile, message));
+```
+
+### 5. Handle Exceptions Appropriately
+
+Let SubModule handle the logging, but catch exceptions when you need custom handling:
+
+```csharp
+// Let SubModule log exceptions
+await SubModule(context, "Risky Operation", async () =>
+{
+ await RiskyOperationAsync(); // Exception will be logged and re-thrown
+});
+
+// Custom exception handling when needed
+var result = await SubModule(context, "Tolerant Operation", async () =>
+{
+ try
+ {
+ return await OperationAsync();
+ }
+ catch (ExpectedException ex)
+ {
+ context.Logger.LogWarning("Expected failure: {Message}", ex.Message);
+ return DefaultValue;
+ }
+});
+```
+
+## Error Handling
+
+SubModule automatically:
+1. Logs exceptions with LogError
+2. Includes the module name and sub-task name
+3. Re-throws the exception
+
+**Example Error Log**:
+```
+[ERROR] [SubModule] ProcessFilesModule.file1.txt failed
+System.InvalidOperationException: Unable to process file
+ at ProcessFilesModule....
+```
+
+## Performance Considerations
+
+### Parallel Processing
+
+SubModule works seamlessly with parallel processing libraries:
+
+```csharp
+// Process in parallel with degree of parallelism
+await items.ToAsyncProcessorBuilder()
+ .SelectAsync(item => SubModule(context, item.Name, () => Process(item), cancellationToken))
+ .ProcessInParallel(maxDegreeOfParallelism: 4);
+```
+
+### Progress Tracking
+
+Each SubModule logs start and completion, providing automatic progress tracking:
+
+```
+[SubModule] MyModule.Item1 starting
+[SubModule] MyModule.Item2 starting
+[SubModule] MyModule.Item1 completed
+[SubModule] MyModule.Item3 starting
+[SubModule] MyModule.Item2 completed
+...
+```
+
+## Comparison with Direct Execution
+
+### Without SubModule
+
+```csharp
+foreach (var file in files)
+{
+ context.Logger.LogInformation("Processing {File}", file);
+ try
+ {
+ await ProcessFileAsync(file);
+ context.Logger.LogInformation("Completed {File}", file);
+ }
+ catch (Exception ex)
+ {
+ context.Logger.LogError(ex, "Failed processing {File}", file);
+ throw;
+ }
+}
+```
+
+### With SubModule
+
+```csharp
+foreach (var file in files)
+{
+ await SubModule(context, file, async () =>
+ {
+ await ProcessFileAsync(file);
+ }, cancellationToken);
+}
+```
+
+**Benefits**:
+- Less boilerplate
+- Consistent logging format
+- Automatic exception handling
+- Clear operation boundaries
+
+## Integration with Pipeline Features
+
+### With Retry Policies
+
+SubModule works with module retry policies:
+
+```csharp
+public class RetryModule : Module, IModuleRetryPolicy
+{
+ public IAsyncPolicy GetRetryPolicy()
+ {
+ return Policy
+ .Handle()
+ .RetryAsync(3);
+ }
+
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // SubModule calls will be retried if the module fails
+ return await SubModule(context, "Operation", async () =>
+ {
+ return await RiskyOperationAsync();
+ }, cancellationToken);
+ }
+}
+```
+
+### With Progress Reporting
+
+SubModule automatically participates in pipeline progress reporting:
+
+```csharp
+var options = new PipelineOptions
+{
+ ShowProgressInConsole = true
+};
+
+// SubModule operations will be visible in progress output
+```
+
+## Limitations
+
+1. **No Nested SubModules**: SubModules cannot contain other SubModules
+2. **Same Thread**: SubModules execute on the calling thread
+3. **No Independent Retry**: SubModules cannot have independent retry policies (module-level only)
+4. **No Independent Timeout**: SubModules share the module's timeout
+
+## Migration from v2.x
+
+See the [V2 to V3 Migration Guide](../migration/V2_TO_V3_MIGRATION.md#submodule-api-changes) for detailed migration instructions.
+
+**Quick Summary**:
+```csharp
+// v2.x
+await SubModule("name", action)
+
+// v3.0
+await SubModule(context, "name", action, cancellationToken)
+```
+
+## Related Documentation
+
+- [V2 to V3 Migration Guide](../migration/V2_TO_V3_MIGRATION.md)
+- [Module Documentation](./MODULES.md)
+- [Pipeline Context Documentation](./PIPELINE_CONTEXT.md)
+
+---
+
+**Version**: 3.0.0
+**Last Updated**: 2025-11-09
diff --git a/docs/migration/V2_TO_V3_MIGRATION.md b/docs/migration/V2_TO_V3_MIGRATION.md
new file mode 100644
index 0000000000..e5f89199cd
--- /dev/null
+++ b/docs/migration/V2_TO_V3_MIGRATION.md
@@ -0,0 +1,622 @@
+# ModularPipelines v2 to v3 Migration Guide
+
+## Overview
+
+ModularPipelines v3.0 represents a major architectural shift from inheritance-based modules to composition-based modules. This guide will help you migrate your v2.x pipelines to v3.0.
+
+## Table of Contents
+
+- [Major Changes](#major-changes)
+- [Breaking Changes](#breaking-changes)
+- [Migration Steps](#migration-steps)
+- [Module Base Class Changes](#module-base-class-changes)
+- [SubModule API Changes](#submodule-api-changes)
+- [Common Migration Scenarios](#common-migration-scenarios)
+- [Troubleshooting](#troubleshooting)
+
+## Major Changes
+
+### Architecture: Inheritance to Composition
+
+**v2.x**: Modules inherited from `ModuleBase` which provided numerous built-in methods and properties.
+
+**v3.0**: Modules inherit from minimal `Module` base class. Functionality is accessed through the `IPipelineContext` parameter.
+
+### Benefits of Composition-Based Architecture
+
+1. **Cleaner Dependencies**: Explicit dependencies through method parameters
+2. **Better Testability**: No hidden state in base classes
+3. **Simpler Base Class**: Minimal `Module` with only essential functionality
+4. **Explicit Context**: All pipeline services accessed via `IPipelineContext`
+
+## Breaking Changes
+
+### 1. Module Base Class
+
+| Aspect | v2.x | v3.0 |
+|--------|------|------|
+| Base Class | `ModuleBase` | `Module` |
+| Context Access | Inherited properties | `IPipelineContext` parameter |
+| Execution Method | `ExecuteAsync(context, cancellation)` | `ExecuteAsync(context, cancellation)` |
+
+### 2. SubModule API
+
+| Aspect | v2.x | v3.0 |
+|--------|------|------|
+| Signature | `SubModule(name, action)` | `SubModule(context, name, action, cancellationToken)` |
+| Context | Implicit (from base) | Explicit parameter |
+| Cancellation | Limited | Full support |
+
+### 3. Removed Classes
+
+The following classes have been removed in v3.0:
+
+- `ModuleBase` → Use `Module`
+- `SubModuleFailedException` → Exceptions propagate directly
+- Various base class helper methods → Use `IPipelineContext` extension methods
+
+## Migration Steps
+
+### Step 1: Update Module Base Class
+
+**Before (v2.x)**:
+```csharp
+using ModularPipelines.Modules;
+
+public class MyModule : ModuleBase
+{
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // Your code
+ }
+}
+```
+
+**After (v3.0)**:
+```csharp
+using ModularPipelines.Modules;
+
+public class MyModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // Your code
+ }
+}
+```
+
+**Changes**:
+- `ModuleBase` → `Module`
+- `protected override` → `public override` (if you prefer, though `public` is now required)
+- No other changes to method signature needed
+
+### Step 2: Update SubModule Calls
+
+**Before (v2.x)**:
+```csharp
+public class ProcessFilesModule : ModuleBase
+{
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var files = new[] { "file1.txt", "file2.txt", "file3.txt" };
+
+ return await files.ToAsyncProcessorBuilder()
+ .SelectAsync(file => SubModule(file, async () =>
+ {
+ return await ProcessFile(file);
+ }))
+ .ProcessInParallel();
+ }
+}
+```
+
+**After (v3.0)**:
+```csharp
+public class ProcessFilesModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var files = new[] { "file1.txt", "file2.txt", "file3.txt" };
+
+ return await files.ToAsyncProcessorBuilder()
+ .SelectAsync(file => SubModule(context, file, async () =>
+ {
+ return await ProcessFile(file);
+ }, cancellationToken))
+ .ProcessInParallel();
+ }
+}
+```
+
+**Changes**:
+- Add `context` as first parameter to `SubModule()`
+- Add `cancellationToken` as last parameter to `SubModule()`
+- Change base class from `ModuleBase` to `Module`
+
+### Step 3: Access Pipeline Services via Context
+
+All pipeline services that were previously accessible via base class properties are now accessed through the `IPipelineContext` parameter.
+
+**Before (v2.x)**:
+```csharp
+// Accessing services via base class
+protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+{
+ // These were available directly from base class
+ var gitInfo = await Git.Information;
+ var result = await DotNet.Test(...);
+ Logger.LogInformation("Message");
+}
+```
+
+**After (v3.0)**:
+```csharp
+// Accessing services via context
+public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+{
+ // Access everything through context
+ var gitInfo = await context.Git().Information;
+ var result = await context.DotNet().Test(...);
+ context.Logger.LogInformation("Message");
+}
+```
+
+## Module Base Class Changes
+
+### Property Access Pattern
+
+| Service | v2.x Access | v3.0 Access |
+|---------|-------------|-------------|
+| Git | `Git.*` | `context.Git().*` |
+| DotNet | `DotNet.*` | `context.DotNet().*` |
+| Docker | `Docker.*` | `context.Docker().*` |
+| Logger | `Logger.*` | `context.Logger.*` |
+| File System | `FileSystem.*` | `context.FileSystem.*` |
+| Environment | `Environment.*` | `context.Environment.*` |
+
+### Complete Example
+
+**Before (v2.x)**:
+```csharp
+public class BuildModule : ModuleBase
+{
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("Building project...");
+
+ var solutionFile = FileSystem.RootDirectory
+ .FindFile(x => x.Extension == ".sln");
+
+ return await DotNet.Build(new DotNetBuildOptions
+ {
+ ProjectSolutionDirectoryDllExe = solutionFile
+ }, cancellationToken);
+ }
+}
+```
+
+**After (v3.0)**:
+```csharp
+public class BuildModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ context.Logger.LogInformation("Building project...");
+
+ var solutionFile = context.FileSystem.RootDirectory
+ .FindFile(x => x.Extension == ".sln");
+
+ return await context.DotNet().Build(new DotNetBuildOptions
+ {
+ ProjectSolutionDirectoryDllExe = solutionFile
+ }, cancellationToken);
+ }
+}
+```
+
+## SubModule API Changes
+
+### Overview
+
+The SubModule API has been restored in v3.0 with an improved design that requires explicit context and cancellation token parameters.
+
+### Signature Changes
+
+**v2.x Signatures**:
+```csharp
+// Async with result
+protected Task SubModule(string name, Func> action)
+
+// Sync with result
+protected Task SubModule(string name, Func action)
+
+// Async without result
+protected Task SubModule(string name, Func action)
+
+// Sync without result
+protected Task SubModule(string name, Action action)
+```
+
+**v3.0 Signatures**:
+```csharp
+// Async with result
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func> action,
+ CancellationToken cancellationToken = default)
+
+// Sync with result
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func action,
+ CancellationToken cancellationToken = default)
+
+// Async without result
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Func action,
+ CancellationToken cancellationToken = default)
+
+// Sync without result
+protected Task SubModule(
+ IPipelineContext context,
+ string name,
+ Action action,
+ CancellationToken cancellationToken = default)
+```
+
+### Migration Examples
+
+#### Example 1: Simple Async SubModule
+
+**v2.x**:
+```csharp
+await SubModule("Download File", async () =>
+{
+ await DownloadFileAsync(url);
+});
+```
+
+**v3.0**:
+```csharp
+await SubModule(context, "Download File", async () =>
+{
+ await DownloadFileAsync(url);
+}, cancellationToken);
+```
+
+#### Example 2: SubModule with Return Value
+
+**v2.x**:
+```csharp
+var results = await items.ToAsyncProcessorBuilder()
+ .SelectAsync(item => SubModule(item.Name, async () =>
+ {
+ return await ProcessItemAsync(item);
+ }))
+ .ProcessInParallel();
+```
+
+**v3.0**:
+```csharp
+var results = await items.ToAsyncProcessorBuilder()
+ .SelectAsync(item => SubModule(context, item.Name, async () =>
+ {
+ return await ProcessItemAsync(item);
+ }, cancellationToken))
+ .ProcessInParallel();
+```
+
+#### Example 3: Synchronous SubModule
+
+**v2.x**:
+```csharp
+await SubModule("Calculate", () =>
+{
+ var result = PerformCalculation();
+ Logger.LogInformation("Result: {Result}", result);
+});
+```
+
+**v3.0**:
+```csharp
+await SubModule(context, "Calculate", () =>
+{
+ var result = PerformCalculation();
+ context.Logger.LogInformation("Result: {Result}", result);
+}, cancellationToken);
+```
+
+## Common Migration Scenarios
+
+### Scenario 1: Simple Module
+
+**v2.x**:
+```csharp
+public class HelloWorldModule : ModuleBase
+{
+ protected override Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("Hello, World!");
+ return Task.FromResult("Hello, World!");
+ }
+}
+```
+
+**v3.0**:
+```csharp
+public class HelloWorldModule : Module
+{
+ public override Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ context.Logger.LogInformation("Hello, World!");
+ return Task.FromResult("Hello, World!");
+ }
+}
+```
+
+### Scenario 2: Module with Dependencies
+
+**v2.x**:
+```csharp
+[DependsOn]
+public class TestModule : ModuleBase
+{
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ Logger.LogInformation("Running tests...");
+
+ return await DotNet.Test(new DotNetTestOptions
+ {
+ Configuration = "Release"
+ }, cancellationToken);
+ }
+}
+```
+
+**v3.0**:
+```csharp
+[DependsOn]
+public class TestModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ context.Logger.LogInformation("Running tests...");
+
+ return await context.DotNet().Test(new DotNetTestOptions
+ {
+ Configuration = "Release"
+ }, cancellationToken);
+ }
+}
+```
+
+### Scenario 3: Module with SubModules and Loops
+
+**v2.x**:
+```csharp
+public class PublishModule : ModuleBase
+{
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var projects = FileSystem.RootDirectory
+ .FindFiles(x => x.Name.EndsWith(".csproj"));
+
+ return await projects.ToAsyncProcessorBuilder()
+ .SelectAsync(project => SubModule(project.Name, async () =>
+ {
+ Logger.LogInformation("Publishing {Project}", project.Name);
+ await DotNet.Publish(new DotNetPublishOptions
+ {
+ ProjectSolutionDirectoryDllExe = project
+ }, cancellationToken);
+ return project.Name;
+ }))
+ .ProcessInParallel();
+ }
+}
+```
+
+**v3.0**:
+```csharp
+public class PublishModule : Module
+{
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ var projects = context.FileSystem.RootDirectory
+ .FindFiles(x => x.Name.EndsWith(".csproj"));
+
+ return await projects.ToAsyncProcessorBuilder()
+ .SelectAsync(project => SubModule(context, project.Name, async () =>
+ {
+ context.Logger.LogInformation("Publishing {Project}", project.Name);
+ await context.DotNet().Publish(new DotNetPublishOptions
+ {
+ ProjectSolutionDirectoryDllExe = project
+ }, cancellationToken);
+ return project.Name;
+ }, cancellationToken))
+ .ProcessInParallel();
+ }
+}
+```
+
+### Scenario 4: Module with Skip Logic
+
+**v2.x**:
+```csharp
+public class DeployModule : ModuleBase
+{
+ protected override SkipDecision ShouldSkip(IPipelineContext context)
+ {
+ if (context.Environment.EnvironmentVariables
+ .TryGet("DEPLOY_ENABLED", out var value)
+ && value == "false")
+ {
+ return SkipDecision.Skip("Deployment disabled");
+ }
+
+ return SkipDecision.DoNotSkip;
+ }
+
+ protected override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // Deploy logic
+ }
+}
+```
+
+**v3.0**:
+```csharp
+public class DeployModule : Module, IModuleSkipCondition
+{
+ public SkipDecision ShouldSkip(IPipelineContext context)
+ {
+ if (context.Environment.EnvironmentVariables
+ .TryGet("DEPLOY_ENABLED", out var value)
+ && value == "false")
+ {
+ return SkipDecision.Skip("Deployment disabled");
+ }
+
+ return SkipDecision.DoNotSkip;
+ }
+
+ public override async Task ExecuteAsync(
+ IPipelineContext context,
+ CancellationToken cancellationToken)
+ {
+ // Deploy logic
+ }
+}
+```
+
+**Note**: Skip logic now requires implementing `IModuleSkipCondition` interface.
+
+## Troubleshooting
+
+### Common Issues and Solutions
+
+#### Issue 1: Cannot access Git/DotNet/etc. directly
+
+**Error**:
+```
+CS0103: The name 'Git' does not exist in the current context
+```
+
+**Solution**:
+Access via context: `context.Git()`
+
+#### Issue 2: SubModule signature mismatch
+
+**Error**:
+```
+CS1501: No overload for method 'SubModule' takes 2 arguments
+```
+
+**Solution**:
+Add `context` as first parameter and `cancellationToken` as last parameter:
+```csharp
+// Old: SubModule("name", action)
+// New: SubModule(context, "name", action, cancellationToken)
+```
+
+#### Issue 3: ModuleBase not found
+
+**Error**:
+```
+CS0246: The type or namespace name 'ModuleBase<>' could not be found
+```
+
+**Solution**:
+Change `ModuleBase` to `Module`
+
+#### Issue 4: Protected override ExecuteAsync not recognized
+
+**Error**:
+```
+CS0621: Virtual or abstract members cannot be private
+```
+
+**Solution**:
+Change from `protected override` to `public override`:
+```csharp
+public override async Task ExecuteAsync(...)
+```
+
+## Migration Checklist
+
+Use this checklist to ensure you've migrated all aspects:
+
+- [ ] Changed all `ModuleBase` to `Module`
+- [ ] Changed `protected override` to `public override` for `ExecuteAsync`
+- [ ] Updated all property access from base class to `context.*`
+- [ ] Updated all `Git.*` to `context.Git().*`
+- [ ] Updated all `DotNet.*` to `context.DotNet().*`
+- [ ] Updated all `Logger.*` to `context.Logger.*`
+- [ ] Updated all `FileSystem.*` to `context.FileSystem.*`
+- [ ] Updated all SubModule calls to include `context` and `cancellationToken`
+- [ ] Implemented `IModuleSkipCondition` if using `ShouldSkip`
+- [ ] Implemented `IModuleRetryPolicy` if using retry logic
+- [ ] Tested all modules compile successfully
+- [ ] Tested all modules execute correctly
+- [ ] Updated any custom base classes
+- [ ] Updated documentation
+
+## Getting Help
+
+If you encounter issues during migration:
+
+1. **Check Documentation**: Review the v3.0 documentation at the ModularPipelines website
+2. **Build Pipeline**: Check `src/ModularPipelines.Build/` for real-world v3.0 modules
+3. **GitHub Issues**: Report bugs or ask questions at https://github.com/thomhurst/ModularPipelines/issues
+
+## Summary
+
+The v2 to v3 migration primarily involves:
+
+1. **Base Class**: `ModuleBase` → `Module`
+2. **Context Access**: Direct property access → `context.*`
+3. **SubModule**: Add `context` and `cancellationToken` parameters
+4. **Skip Logic**: Implement `IModuleSkipCondition` interface
+
+The composition-based architecture provides better testability, cleaner dependencies, and a more maintainable codebase. While the migration requires updates to your modules, the changes are straightforward and follow consistent patterns.
+
+---
+
+**Version**: 3.0.0
+**Last Updated**: 2025-11-09
+**Breaking Changes**: Yes - API changes required
diff --git a/errors.txt b/errors.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fix_migration_errors.py b/fix_migration_errors.py
new file mode 100644
index 0000000000..7ef02886d6
--- /dev/null
+++ b/fix_migration_errors.py
@@ -0,0 +1,197 @@
+#!/usr/bin/env python3
+"""
+Script to fix migration errors in test modules
+"""
+import re
+import os
+from pathlib import Path
+
+def fix_file(file_path):
+ """Fix migration errors in a single file"""
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ original_content = content
+
+ # Fix 1: Change remaining `protected` to `public` for ExecuteAsync
+ content = re.sub(
+ r'protected async Task<(.+?)>\s+ExecuteAsync\(',
+ r'public override async Task<\1> ExecuteAsync(',
+ content
+ )
+
+ # Fix 2: Remove Timeout property and add IModuleTimeout interface
+ # Pattern: protected internal override TimeSpan Timeout { get; } = TimeSpan.FromSeconds(X);
+ timeout_pattern = r'(\s+)(protected internal override|protected override|public override)\s+TimeSpan\s+Timeout\s+\{\s+get;\s+\}\s*=\s*TimeSpan\.FromSeconds\((\d+(?:\.\d+)?)\);'
+
+ def add_timeout_interface(match):
+ indent = match.group(1)
+ seconds = match.group(3)
+ # Return a GetTimeout method implementation
+ return f'{indent}public TimeSpan GetTimeout() => TimeSpan.FromSeconds({seconds});'
+
+ # Check if file has Timeout property
+ if re.search(timeout_pattern, content):
+ content = re.sub(timeout_pattern, add_timeout_interface, content)
+
+ # Add IModuleTimeout to the class declaration if Timeout was found
+ content = re.sub(
+ r'(class\s+\w+\s*:\s*ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleTimeout\n {',
+ content
+ )
+
+ # Add using statement for behaviors
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ # Find the last using statement and add after it
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ # Fix 3: Handle TimeSpan.Zero timeout
+ timeout_zero_pattern = r'(\s+)(protected internal override|protected override|public override)\s+TimeSpan\s+Timeout\s+=>\s+TimeSpan\.Zero;'
+
+ def add_timeout_zero_interface(match):
+ indent = match.group(1)
+ return f'{indent}public TimeSpan GetTimeout() => TimeSpan.Zero;'
+
+ if re.search(timeout_zero_pattern, content):
+ content = re.sub(timeout_zero_pattern, add_timeout_zero_interface, content)
+
+ # Add IModuleTimeout to the class declaration
+ content = re.sub(
+ r'(class\s+\w+\s*:\s*ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleTimeout\n {',
+ content
+ )
+
+ # Add using statement for behaviors
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ # Fix 4: Convert ShouldSkip method to ShouldSkipAsync with IModuleSkipLogic interface
+ shouldskip_pattern = r'(\s+)(protected override|public override)\s+Task\s+ShouldSkip\(IPipelineContext\s+context\)'
+
+ if re.search(shouldskip_pattern, content):
+ content = re.sub(
+ shouldskip_pattern,
+ r'\1public Task ShouldSkipAsync(IPipelineContext context)',
+ content
+ )
+
+ # Add IModuleSkipLogic to class declaration
+ content = re.sub(
+ r'(class\s+\w+\s*:\s*ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleSkipLogic\n {',
+ content
+ )
+
+ # Add using statement for behaviors
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ # Fix 5: Convert ShouldIgnoreFailures to ShouldIgnoreFailuresAsync with IModuleErrorHandling
+ shouldignore_pattern = r'(\s+)(protected override|public override)\s+Task\s+ShouldIgnoreFailures\(IPipelineContext\s+context,\s+Exception\s+exception\)'
+
+ if re.search(shouldignore_pattern, content):
+ content = re.sub(
+ shouldignore_pattern,
+ r'\1public Task ShouldIgnoreFailuresAsync(IPipelineContext context, Exception exception)',
+ content
+ )
+
+ # Add IModuleErrorHandling to class declaration
+ content = re.sub(
+ r'(class\s+\w+\s*:\s*ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleErrorHandling\n {',
+ content
+ )
+
+ # Add using statement for behaviors
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ # Fix 6: Convert RetryPolicy to GetRetryPolicy with IModuleRetryPolicy
+ retrypolicy_pattern = r'(\s+)(protected override|public override)\s+AsyncRetryPolicy<[^>]+>\s+RetryPolicy\s+\{[^\}]+\}'
+
+ if re.search(retrypolicy_pattern, content):
+ # This is complex - need to convert property to method
+ def convert_retry_policy(match):
+ indent = match.group(1)
+ policy_content = match.group(0)
+
+ # Extract the policy expression
+ policy_match = re.search(r'=\s*(.+?);', policy_content, re.DOTALL)
+ if policy_match:
+ policy_expr = policy_match.group(1).strip()
+ return f'{indent}public IAsyncPolicy GetRetryPolicy() => {policy_expr};'
+ return policy_content
+
+ content = re.sub(retrypolicy_pattern, convert_retry_policy, content, flags=re.DOTALL)
+
+ # Add IModuleRetryPolicy to class declaration
+ content = re.sub(
+ r'(class\s+\w+\s*:\s*ModuleNew(?:<[^>]+>)?)\s*\{',
+ r'\1, IModuleRetryPolicy\n {',
+ content
+ )
+
+ # Add using statement for behaviors
+ if 'using ModularPipelines.Modules.Behaviors;' not in content:
+ last_using = list(re.finditer(r'^using [^;]+;', content, re.MULTILINE))
+ if last_using:
+ pos = last_using[-1].end()
+ content = content[:pos] + '\nusing ModularPipelines.Modules.Behaviors;' + content[pos:]
+
+ # Fix 7: Handle duplicate interface additions (clean up)
+ content = re.sub(r'(, IModule\w+)+', lambda m: ', '.join(sorted(set(m.group(0).split(', ')))), content)
+
+ if content != original_content:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+ return True
+
+ return False
+
+def main():
+ """Main fix function"""
+ test_files = [
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\ModuleTimeoutTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\EngineCancellationTokenTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\FailedPipelineTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\IgnoredFailureTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\ModuleHistoryTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\RetryTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\SkippedModuleTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\SkipDependabotAttributeTests.cs'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests\ResultsRepositoryTests.cs'),
+ ]
+
+ total_fixed = 0
+
+ for file_path in test_files:
+ if not file_path.exists():
+ print(f"SKIP (not found): {file_path}")
+ continue
+
+ print(f"Fixing: {file_path.name}")
+ if fix_file(file_path):
+ total_fixed += 1
+ print(f" OK Fixed")
+ else:
+ print(f" No changes needed")
+
+ print(f"\nTotal files fixed: {total_fixed}")
+
+if __name__ == '__main__':
+ main()
diff --git a/migrate_test_modules.py b/migrate_test_modules.py
new file mode 100644
index 0000000000..7e57efc9bd
--- /dev/null
+++ b/migrate_test_modules.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""
+Script to migrate test modules from Module to ModuleNew
+"""
+import re
+import os
+from pathlib import Path
+
+def migrate_file(file_path):
+ """Migrate a single file from Module to ModuleNew"""
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ original_content = content
+
+ # Skip files that use SubModule (they need manual migration)
+ if 'SubModule' in content:
+ print(f" SKIP (uses SubModule): {file_path}")
+ return 0
+
+ # Count modules before migration
+ module_count = len(re.findall(r': Module(?:<|(?:\s|$))', content))
+
+ # Pattern 1: Change `: Module` to `: ModuleNew` (non-generic)
+ content = re.sub(r'(\s+class\s+\w+\s*):(\s*)Module(\s|$)', r'\1:\2ModuleNew\3', content)
+
+ # Pattern 2: Change `: Module` to `: ModuleNew` (generic)
+ content = re.sub(r'(\s+class\s+\w+\s*):(\s*)Module<', r'\1:\2ModuleNew<', content)
+
+ # Pattern 3: Change `protected override` to `public override` for ExecuteAsync
+ content = re.sub(
+ r'protected override async Task<(.+?)>\s+ExecuteAsync\(',
+ r'public override async Task<\1> ExecuteAsync(',
+ content
+ )
+
+ # Pattern 4: Replace `await GetModule()` with `await context.GetModuleAsync()`
+ content = re.sub(r'\bawait GetModule<', r'await context.GetModuleAsync<', content)
+
+ # Pattern 5: Replace `GetModuleIfRegistered()` with `await context.GetModuleIfRegisteredAsync()`
+ content = re.sub(r'\bGetModuleIfRegistered<', r'await context.GetModuleIfRegisteredAsync<', content)
+
+ # Pattern 6: Replace `return await NothingAsync();` with `await Task.CompletedTask; return null;`
+ content = re.sub(
+ r'return await NothingAsync\(\);',
+ 'await Task.CompletedTask;\n return null;',
+ content
+ )
+
+ # Pattern 7: Remove ModuleRunType property and add [AlwaysRun] attribute
+ # First, find classes with ModuleRunType property
+ class_pattern = r'((?:\[[\w\.<>]+(?:\([^\]]*\))?\]\s*)*)(public|private)\s+class\s+(\w+)\s*:\s*ModuleNew(?:<[^>]+>)?\s*\{([^}]+)public override ModuleRunType ModuleRunType => ModuleRunType\.AlwaysRun;'
+
+ def add_always_run_attribute(match):
+ attributes = match.group(1)
+ visibility = match.group(2)
+ class_name = match.group(3)
+ class_body = match.group(4)
+
+ # Add [AlwaysRun] attribute if not already present
+ if '[AlwaysRun]' not in attributes:
+ attributes = attributes.rstrip() + '\n [AlwaysRun]\n '
+
+ return f'{attributes}{visibility} class {class_name} : ModuleNew\n {{{class_body}'
+
+ content = re.sub(class_pattern, add_always_run_attribute, content, flags=re.DOTALL)
+
+ # Pattern 8: Update test assertions from `result.Result.Value` to `result.Value`
+ content = re.sub(r'\.Result\.Value', r'.Value', content)
+
+ if content != original_content:
+ with open(file_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+ return module_count
+
+ return 0
+
+def main():
+ """Main migration function"""
+ test_dirs = [
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.UnitTests'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.Azure.UnitTests'),
+ Path(r'C:\git\ModularPipelines\test\ModularPipelines.TestHelpers'),
+ Path(r'C:\git\ModularPipelines\src\ModularPipelines.Analyzers\ModularPipelines.Analyzers.Test'),
+ ]
+
+ total_files = 0
+ total_modules = 0
+ skipped_files = []
+
+ for test_dir in test_dirs:
+ if not test_dir.exists():
+ continue
+
+ print(f"\nProcessing directory: {test_dir}")
+
+ for file_path in test_dir.rglob('*.cs'):
+ # Skip obj directories
+ if 'obj' in file_path.parts or 'bin' in file_path.parts:
+ continue
+
+ # Check if file contains Module classes
+ with open(file_path, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ if re.search(r': Module(?:<|(?:\s|$))', content):
+ print(f"\nMigrating: {file_path.relative_to(test_dir.parent)}")
+ migrated_count = migrate_file(file_path)
+ if migrated_count > 0:
+ total_files += 1
+ total_modules += migrated_count
+ print(f" OK Migrated {migrated_count} module(s)")
+ elif 'SubModule' in content:
+ skipped_files.append(str(file_path))
+
+ print(f"\n{'='*60}")
+ print(f"Migration complete!")
+ print(f"Total files migrated: {total_files}")
+ print(f"Total modules migrated: {total_modules}")
+
+ if skipped_files:
+ print(f"\nSkipped files (using SubModule):")
+ for f in skipped_files:
+ print(f" - {f}")
+
+if __name__ == '__main__':
+ main()
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs
index 741c671114..51753029e7 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAsyncModulesUnitTests.cs
@@ -88,7 +88,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
return await ExecuteCommand(context);
}
@@ -166,7 +166,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- {|#0:protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ {|#0:public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
if (1 + ""n"" == ""1n"")
{
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs
index 9f1274865b..8f5ad7ac3c 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersAwaitThisUnitTests.cs
@@ -24,7 +24,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// This should trigger the analyzer
{|#0:await this|};
@@ -50,7 +50,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
return await ExecuteCommand(context);
}
@@ -81,7 +81,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
// This is fine - awaiting something else
var otherModule = GetModule();
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs
index af9dd04140..c37bb100d9 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersBaseClassAttributeUnitTests.cs
@@ -23,7 +23,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return null;
@@ -32,7 +32,7 @@ public class Module1 : Module
public class Module2 : DependsOnModule1
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
var module1 = await {|#0:GetModule()|};
return null;
@@ -60,7 +60,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return null;
@@ -69,7 +69,7 @@ public class Module1 : Module
public class Module2 : Module, IDependsOnModule1
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
var module1 = await {|#0:GetModule()|};
return null;
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs
index fb4cffc71b..bbfb0262de 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConflictingDependsOnAttributeUnitTests.cs
@@ -24,7 +24,7 @@ namespace ModularPipelines.Examples.Modules;
[{|#0:DependsOn|}]
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -34,7 +34,7 @@ public class Module1 : Module>
[{|#1:DependsOn|}]
public class Module2 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -60,7 +60,7 @@ namespace ModularPipelines.Examples.Modules;
[{|#0:DependsOn|}]
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -85,7 +85,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -95,7 +95,7 @@ public class Module1 : Module>
[DependsOn]
public class Module2 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs
index ac67c3ca4b..d66d6a1bba 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersConsoleUnitTests.cs
@@ -22,7 +22,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
@@ -49,7 +49,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
@@ -76,7 +76,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
@@ -103,7 +103,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
@@ -130,7 +130,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
@@ -157,7 +157,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs
index 43b88ebe08..2ebe032fc9 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersIEnumerableUnitTests.cs
@@ -22,7 +22,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : {|#0:Module>|}
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new ModuleResult>(Array.Empty().Select(x => x));
@@ -46,7 +46,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs
index fc4bc21f44..6c1a084e78 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersILoggerUnitTests.cs
@@ -27,7 +27,7 @@ public Module1({|#0:ILogger logger|})
{
}
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -56,7 +56,7 @@ public Module1({|#0:ILoggerProvider loggerProvider|})
{
}
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -85,7 +85,7 @@ public Module1({|#0:ILoggerFactory loggerFactory|})
{
}
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -114,7 +114,7 @@ public Module1({|#0:ILogger logger|})
{
}
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -139,7 +139,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
@@ -169,7 +169,7 @@ public Module1(IModuleLoggerProvider moduleLoggerProvider)
{
}
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return new List();
diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersUnitTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersUnitTests.cs
index f09a239435..75b547a9ba 100644
--- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersUnitTests.cs
+++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/ModularPipelinesAnalyzersUnitTests.cs
@@ -22,7 +22,7 @@ namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return null;
@@ -31,7 +31,7 @@ public class Module1 : Module
public class Module2 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
var module1 = await {|#0:GetModule()|};
return null;
@@ -51,7 +51,7 @@ public class Module2 : Module
namespace ModularPipelines.Examples.Modules;
public class Module1 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
await Task.Delay(1, cancellationToken);
return null;
@@ -61,9 +61,9 @@ public class Module1 : Module
[DependsOn]
public class Module2 : Module
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
- var module1 = await GetModule();
+ var module1 = await context.GetModuleAsync();
return null;
}
}";
diff --git a/src/ModularPipelines.Build/Modules/ChangedFilesInPullRequestModule.cs b/src/ModularPipelines.Build/Modules/ChangedFilesInPullRequestModule.cs
index e187aed7f7..650228f66a 100644
--- a/src/ModularPipelines.Build/Modules/ChangedFilesInPullRequestModule.cs
+++ b/src/ModularPipelines.Build/Modules/ChangedFilesInPullRequestModule.cs
@@ -12,7 +12,7 @@ namespace ModularPipelines.Build.Modules;
[SkipOnMainBranch]
public class ChangedFilesInPullRequestModule : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
var result = await context.Git().Commands.Diff(new GitDiffOptions
{
diff --git a/src/ModularPipelines.Build/Modules/CodeFormattedNicelyModule.cs b/src/ModularPipelines.Build/Modules/CodeFormattedNicelyModule.cs
index 39b9f86fe0..a559c81771 100644
--- a/src/ModularPipelines.Build/Modules/CodeFormattedNicelyModule.cs
+++ b/src/ModularPipelines.Build/Modules/CodeFormattedNicelyModule.cs
@@ -11,6 +11,7 @@
using ModularPipelines.GitHub.Extensions;
using ModularPipelines.Models;
using ModularPipelines.Modules;
+using ModularPipelines.Modules.Behaviors;
namespace ModularPipelines.Build.Modules;
@@ -18,9 +19,9 @@ namespace ModularPipelines.Build.Modules;
[SkipIfNoStandardGitHubToken]
[SkipOnMainBranch]
[RunOnLinuxOnly]
-public class CodeFormattedNicelyModule : Module
+[AlwaysRun]
+public class CodeFormattedNicelyModule : Module, IModuleSkipLogic, IModuleErrorHandling
{
- public override ModuleRunType ModuleRunType => ModuleRunType.AlwaysRun;
private const string DotnetFormatGitMessage = "DotNet Format";
@@ -31,7 +32,16 @@ public CodeFormattedNicelyModule(IOptions githubSettings)
_githubSettings = githubSettings;
}
- protected override Task ShouldSkip(IPipelineContext context)
+ public Task ShouldIgnoreFailuresAsync(IPipelineContext context, Exception exception)
+ {
+ var shouldIgnore = exception.Message.Contains("Authentication failed", StringComparison.OrdinalIgnoreCase) ||
+ exception.Message.Contains("Invalid username or token", StringComparison.OrdinalIgnoreCase) ||
+ exception.Message.Contains("Password authentication is not supported", StringComparison.OrdinalIgnoreCase);
+
+ return Task.FromResult(shouldIgnore);
+ }
+
+ public Task ShouldSkipAsync(IPipelineContext context)
{
if (context.GitHub().EnvironmentVariables.EventName != "pull_request")
{
@@ -47,11 +57,11 @@ protected override Task ShouldSkip(IPipelineContext context)
}
///
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
if (!context.Git().Information.BranchName!.Contains("pull"))
{
- return await NothingAsync();
+ return null;
}
try
diff --git a/src/ModularPipelines.Build/Modules/CreateReleaseModule.cs b/src/ModularPipelines.Build/Modules/CreateReleaseModule.cs
index 53ac28e7fa..6e2a4bba7d 100644
--- a/src/ModularPipelines.Build/Modules/CreateReleaseModule.cs
+++ b/src/ModularPipelines.Build/Modules/CreateReleaseModule.cs
@@ -8,6 +8,7 @@
using ModularPipelines.GitHub.Extensions;
using ModularPipelines.Models;
using ModularPipelines.Modules;
+using ModularPipelines.Modules.Behaviors;
using Octokit;
namespace ModularPipelines.Build.Modules;
@@ -18,7 +19,7 @@ namespace ModularPipelines.Build.Modules;
[DependsOn]
[DependsOn]
[DependsOn]
-public class CreateReleaseModule : Module
+public class CreateReleaseModule : Module, IModuleErrorHandling, IModuleSkipLogic
{
private readonly IOptions _githubSettings;
private readonly IOptions _publishSettings;
@@ -30,14 +31,14 @@ public CreateReleaseModule(IOptions githubSettings,
_publishSettings = publishSettings;
}
- protected override async Task ShouldIgnoreFailures(IPipelineContext context, Exception exception)
+ public async Task ShouldIgnoreFailuresAsync(IPipelineContext context, Exception exception)
{
await Task.Yield();
return exception is ApiValidationException;
}
///
- protected override async Task ShouldSkip(IPipelineContext context)
+ public async Task ShouldSkipAsync(IPipelineContext context)
{
await Task.CompletedTask;
@@ -50,9 +51,9 @@ protected override async Task ShouldSkip(IPipelineContext context)
}
///
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
- var versionInfoResult = await GetModule();
+ var versionInfoResult = await context.GetModuleAsync();
return await context.GitHub().Client.Repository.Release.Create(long.Parse(context.GitHub().EnvironmentVariables.RepositoryId!),
new NewRelease($"v{versionInfoResult.Value}")
diff --git a/src/ModularPipelines.Build/Modules/DependabotCommitsModule.cs b/src/ModularPipelines.Build/Modules/DependabotCommitsModule.cs
index 7ada96ada7..1ca0f2fa00 100644
--- a/src/ModularPipelines.Build/Modules/DependabotCommitsModule.cs
+++ b/src/ModularPipelines.Build/Modules/DependabotCommitsModule.cs
@@ -12,7 +12,7 @@ namespace ModularPipelines.Build.Modules;
[RunOnLinuxOnly]
public class DependabotCommitsModule : Module>
{
- protected override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
var repositoryInfo = context.GitHub().RepositoryInfo;
diff --git a/src/ModularPipelines.Build/Modules/FindProjectDependenciesModule.cs b/src/ModularPipelines.Build/Modules/FindProjectDependenciesModule.cs
index db14d87b3d..42b10eef34 100644
--- a/src/ModularPipelines.Build/Modules/FindProjectDependenciesModule.cs
+++ b/src/ModularPipelines.Build/Modules/FindProjectDependenciesModule.cs
@@ -10,9 +10,9 @@ namespace ModularPipelines.Build.Modules;
public class FindProjectDependenciesModule : Module
{
///
- protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
+ public override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
{
- var projects = await GetModule();
+ var projects = await context.GetModuleAsync