Rulejavascript
Developer Cli Rule
Rules for implementing Developer CLI commands
Developer Command Line Interface Rules
Guidelines for implementing and extending the custom Developer Command Line Interface (CLI) commands.
Implementation
-
Command Structure:
- Create one file per command in
developer-cli/Commands - Name the file with
Commandsuffix and inherit fromSystem.CommandLine.Command - Provide a concise description in the constructor explaining the command's purpose
- Define all command options using
AddOption()in the constructor - Implement the command's logic in a private
Executemethod - Use static methods where appropriate for better organization
- Create one file per command in
-
Command Options:
- Use double-dash (
--) for long names and single-dash (-) for abbreviations - Provide clear, concise descriptions for all options
- Use consistent naming across commands (e.g.,
--self-contained-systemand-s) - Define option types explicitly (e.g.,
Option<bool>,Option<string?>) - For positional arguments, include both positional and named options
- Set default values where appropriate using lambda expressions
- Use double-dash (
-
Prerequisites and Dependencies:
- Always check for required dependencies at the beginning of
Execute - Use
Prerequisite.Ensure()to verify required tools are installed - Common prerequisites:
Prerequisite.DotnetandPrerequisite.Node
- Always check for required dependencies at the beginning of
-
Process Execution:
- Use
ProcessHelperfor all external process execution - Use
ProcessHelper.StartProcess()for simple execution - Use
ProcessHelper.StartProcessWithSystemShell()for shell features - Specify working directory as the second parameter when needed
- Handle process execution errors appropriately
- Use
-
Error Handling:
- Use
try/catchblocks to handle exceptions - Display error messages using
AnsiConsole.MarkupLine()with color formatting - Use
Environment.Exit(1)to exit with non-zero status on errors - Don't throw exceptions—handle them and exit gracefully
- Provide clear, actionable error messages
- Use
-
Console Output:
- Use
Spectre.Console.AnsiConsolefor all console output - Use color coding:
[blue]info,[green]success,[yellow]warnings,[red]errors - Format output consistently across all commands
- Use tables, panels, or other Spectre.Console features for complex output
- Use
-
Command Registration:
- Set the command handler in the constructor using
CommandHandler.Create<>() - Match handler parameters with command options
- Use nullable types for optional parameters
- Set the command handler in the constructor using
-
Utility Classes:
- Use existing utility classes from
developer-cli/Utilities - Only create new utility classes for truly generic functionality
- Place command-specific helper methods as private methods in the command class
- Use existing utility classes from
-
Performance Tracking:
- Use
Stopwatchto track execution time for long-running operations - Display timing information for better feedback
- Format timing consistently using extension methods like
.Format()
- Use
-
Self-Contained Implementation:
- Keep each command self-contained in a single file
- Avoid dependencies between command implementations
- Extract shared functionality to utility classes only when necessary
Examples
// ✅ DO: Use clear option naming, prerequisite checks, AnsiConsole, ProcessHelper
public class BuildCommand : Command
{
public BuildCommand() : base("build", "Builds the solution")
{
AddOption(new Option<string?>(["<self-contained-system>", "--self-contained-system", "-s"], "The self-contained system to build")); // ✅ DO: Consistent option naming
AddOption(new Option<bool>(["--verbose", "-v"], () => false, "Enable verbose output"));
Handler = CommandHandler.Create<string?, bool>(Execute);
}
private static void Execute(string? solutionName, bool verbose)
{
Prerequisite.Ensure(Prerequisite.Dotnet); // ✅ DO: Check prerequisites
if (string.IsNullOrEmpty(solutionName))
{
AnsiConsole.MarkupLine("[red]Error: Solution name is required[/]");
Environment.Exit(1); // ✅ DO: Exit on error
}
try
{
AnsiConsole.MarkupLine("[blue]Building solution...[/]"); // ✅ DO: Use AnsiConsole
ProcessHelper.StartProcess($"dotnet build {solutionName}"); // ✅ DO: Use ProcessHelper
AnsiConsole.MarkupLine("[green]Build completed successfully[/]");
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"[red]Error: {ex.Message}[/]");
Environment.Exit(1);
}
}
}
public class BadBuildCommand : Command
{
public BadBuildCommand() : base("bad-build", "Bad build command")
{
// ❌ Use inconsistent option naming (single dash for long names, double dash for short)
AddOption(new Option<string?>(["-file-name", "--f"], "The name of the solution to process"));
Handler = CommandHandler.Create<string>(Execute);
}
private static int Execute(string file)
{
// ❌ DON'T: Skip prerequisite checks
if (string.IsNullOrEmpty(file)) throw new ArgumentException("File required"); // ❌ DON'T: Throw exceptions
Console.WriteLine("Building..."); // ❌ DON'T: Use Console.WriteLine
var process = System.Diagnostics.Process.Start("dotnet", $"build {file}"); // ❌ DON'T: Use Process.Start directly
process.WaitForExit();
return process.ExitCode; // ❌ DON'T: Return exit code, use Environment.Exit instead
}
}
Troubleshooting
The CLI is self-compiling, so to build use execute_command(command: "build", cli: true). Sometimes you will get errors like:
Failed to publish new CLI. Please run 'dotnet run' to fix. Could not load file or assembly 'System.IO.Pipelines,
Version=9.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the
Just retry the command and it should work.