Process Wrapper Example
Examples of wrapping external CLI tools using ProcessCommand.
Git Wrapper
csharp
using Promty;
using Promty.Attributes;
[Description("git", "Execute git commands")]
public class GitCommand : ProcessCommand
{
protected override string ExecutablePath => "git";
}Usage:
bash
dotnet run -- git status
dotnet run -- git add .
dotnet run -- git commit -m "Initial commit"
dotnet run -- git push origin mainDocker Wrapper
csharp
[Description("docker", "Execute docker commands")]
public class DockerCommand : ProcessCommand
{
protected override string ExecutablePath => "docker";
}Usage:
bash
dotnet run -- docker ps
dotnet run -- docker build -t myapp .
dotnet run -- docker run -p 8080:80 myapp
dotnet run -- docker logs mycontainerCustom Build Script Wrapper
csharp
[Description("build-legacy", "Run the legacy build script")]
public class BuildLegacyCommand : ProcessCommand
{
protected override string ExecutablePath => "./scripts/build.sh";
}Usage:
bash
dotnet run -- build-legacy --target productionCross-Platform Python Wrapper
csharp
[Description("python", "Execute Python scripts")]
public class PythonCommand : ProcessCommand
{
protected override string ExecutablePath =>
Environment.OSVersion.Platform == PlatformID.Win32NT
? "python.exe"
: "python3";
}Usage:
bash
dotnet run -- python script.py --arg value
dotnet run -- python -m pip install requestsNPM Wrapper with Default Arguments
Override GetArguments to add default arguments:
csharp
[Description("npm-install", "Install npm packages with preferred settings")]
public class NpmInstallCommand : ProcessCommand
{
protected override string ExecutablePath => "npm";
protected override string[] GetArguments(string[] args)
{
// Always use "npm install" with --save flag
var defaultArgs = new[] { "install", "--save" };
return defaultArgs.Concat(args).ToArray();
}
}Usage:
bash
# Runs: npm install --save express
dotnet run -- npm-install express
# Runs: npm install --save express body-parser
dotnet run -- npm-install express body-parserKubectl Wrapper with Namespace
csharp
[Description("k8s", "Execute kubectl commands with default namespace")]
public class KubectlCommand : ProcessCommand
{
protected override string ExecutablePath => "kubectl";
protected override string[] GetArguments(string[] args)
{
// Add default namespace unless already specified
if (!args.Any(a => a == "-n" || a == "--namespace"))
{
return new[] { "--namespace", "production" }
.Concat(args)
.ToArray();
}
return args;
}
}Usage:
bash
# Uses production namespace by default
dotnet run -- k8s get pods
# Override with custom namespace
dotnet run -- k8s get pods -n stagingTerraform Wrapper with Working Directory
csharp
[Description("tf", "Execute Terraform commands in infrastructure directory")]
public class TerraformCommand : ProcessCommand
{
protected override string ExecutablePath => "terraform";
public override async Task<int> ExecuteAsync(string[] args)
{
// Change to infrastructure directory
var originalDir = Directory.GetCurrentDirectory();
var infraDir = Path.Combine(originalDir, "infrastructure");
if (!Directory.Exists(infraDir))
{
Console.WriteLine($"Error: Infrastructure directory not found: {infraDir}");
return 1;
}
try
{
Directory.SetCurrentDirectory(infraDir);
Console.WriteLine($"Working directory: {infraDir}");
return await base.ExecuteAsync(args);
}
finally
{
Directory.SetCurrentDirectory(originalDir);
}
}
}Usage:
bash
dotnet run -- tf init
dotnet run -- tf plan
dotnet run -- tf apply -auto-approveComposite Command (Multiple Tools)
For complex workflows, use a standard command that runs multiple processes:
csharp
[Description("deploy", "Build, test, and deploy the application")]
public class DeployCommand : Command<DeployCommand.Args>
{
public class Args
{
[FlagAlias("environment", 'e')]
[Description("Target environment (dev, staging, prod)")]
public string Environment { get; set; } = "dev";
[FlagAlias("skip-tests", 't')]
[Description("Skip running tests")]
public bool SkipTests { get; set; }
}
public override async Task<int> ExecuteAsync(Args args)
{
Console.WriteLine($"Deploying to: {args.Environment}");
Console.WriteLine();
// Step 1: Build
Console.WriteLine("Step 1: Building...");
var buildResult = await RunProcess("dotnet", "build -c Release");
if (buildResult != 0)
{
Console.WriteLine("Build failed!");
return buildResult;
}
Console.WriteLine("✓ Build successful");
Console.WriteLine();
// Step 2: Test (optional)
if (!args.SkipTests)
{
Console.WriteLine("Step 2: Running tests...");
var testResult = await RunProcess("dotnet", "test -c Release");
if (testResult != 0)
{
Console.WriteLine("Tests failed!");
return testResult;
}
Console.WriteLine("✓ Tests passed");
Console.WriteLine();
}
// Step 3: Deploy
Console.WriteLine($"Step 3: Deploying to {args.Environment}...");
var deployResult = await RunProcess(
"./deploy.sh",
args.Environment
);
if (deployResult != 0)
{
Console.WriteLine("Deployment failed!");
return deployResult;
}
Console.WriteLine("✓ Deployment successful");
Console.WriteLine();
Console.WriteLine($"Successfully deployed to {args.Environment}!");
return 0;
}
private async Task<int> RunProcess(string executable, string arguments)
{
var process = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = executable,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false
}
};
process.Start();
await process.WaitForExitAsync();
return process.ExitCode;
}
}Usage:
bash
# Deploy to dev
dotnet run -- deploy
# Deploy to production, skip tests
dotnet run -- deploy -e prod --skip-testsOutput:
Deploying to: prod
Step 1: Building...
✓ Build successful
Step 3: Deploying to prod...
✓ Deployment successful
Successfully deployed to prod!When to Use Process Commands
✅ Use ProcessCommand when:
- Wrapping existing CLI tools
- Forwarding all arguments unchanged
- Creating simple shortcuts
- No argument parsing needed
✅ Use Standard Command when:
- You need typed argument binding
- Custom validation required
- Running multiple processes
- Complex business logic
Best Practices
- Check if executable exists - Provide helpful error messages
- Handle exit codes - Forward them appropriately
- Use full paths - When executable not in PATH
- Cross-platform support - Check OS when needed
- Add default arguments - Override
GetArguments()for convenience
