C#Program.csView on GitHub// Module build pipeline: phases, sixteen steps, compiler lanes and captured output.
using System.Globalization;
using ConsoleMonster;
using ConsoleMonster.Layout;
using ConsoleMonster.Live;
using ConsoleMonster.Styling;
using ConsoleMonster.Widgets;
// Data: all tool messages and outcomes are fictional. No build, signing, publishing or file writes occur.
BuildStep[] steps = {
new("Prepare", "config", "Read Build/project.build.json", 0.4, StatusKind.Pass, "Loaded project.build.json (3 projects, version 2.0.X)", "Resolved version 2.0.0 from PowerShell Gallery (current 0.0.4)"),
new("Prepare", "manifest", "Update ConsoleMonster.psd1", 0.5, StatusKind.Pass, "ModuleVersion 0.0.4 -> 2.0.0", "CmdletsToExport: 38 binary cmdlets"),
new("Restore", "restore", "Restore NuGet packages", 1.6, StatusKind.Pass, "Restored src\\Orion\\Orion.csproj (in 412 ms)", "Restored src\\Orion.Shell\\Orion.Shell.csproj (in 380 ms)"),
new("Build", "core-ns", "Compile ConsoleMonster (netstandard2.0)", 1.8, StatusKind.Pass, "ConsoleMonster -> bin\\Release\\netstandard2.0\\ConsoleMonster.dll"),
new("Build", "core-fx", "Compile ConsoleMonster (net472)", 1.5, StatusKind.Pass, "ConsoleMonster -> bin\\Release\\net472\\ConsoleMonster.dll"),
new("Build", "core-net", "Compile ConsoleMonster (net8.0)", 1.4, StatusKind.Pass, "ConsoleMonster -> bin\\Release\\net8.0\\ConsoleMonster.dll"),
new("Build", "ps", "Compile ConsoleMonster.PowerShell", 1.9, StatusKind.Pass, "ConsoleMonster.PowerShell -> bin\\Release\\net472\\ConsoleMonster.PowerShell.dll", "ConsoleMonster.PowerShell -> bin\\Release\\net8.0\\ConsoleMonster.PowerShell.dll"),
new("Test", "xunit", "Run xUnit tests (net472, net8.0)", 3.2, StatusKind.Pass, "Passed! - Failed: 0, Passed: 286, Skipped: 0 - ConsoleMonster.Tests.dll (net472)", "Passed! - Failed: 0, Passed: 286, Skipped: 0 - ConsoleMonster.Tests.dll (net8.0)"),
new("Test", "compatibility", "Run compatibility tests", 2.4, StatusKind.Pass, "Tests completed in 2.1s", "Tests Passed: 41, Failed: 0, Skipped: 0"),
new("Test", "pester7", "Run Pester tests (PowerShell 7.5)", 2.1, StatusKind.Warn, "Tests completed in 1.8s", "Tests Passed: 40, Failed: 0, Skipped: 1 (requires conhost)"),
new("Docs", "md", "Generate markdown help (38 commands)", 1.7, StatusKind.Pass, "Docs\\Readme.md updated", "Docs\\Write-ConsoleTable.md ... Docs\\Start-ConsoleDashboard.md"),
new("Docs", "maml", "Generate en-US MAML help", 0.9, StatusKind.Pass, "en-US\\ConsoleMonster.PowerShell.dll-Help.xml (412 KB)"),
new("Sign", "sign", "Sign module files", 0.3, StatusKind.Skip, "SignModule is $false for this build; skipping Authenticode signing"),
new("Package", "nupkg", "Pack ConsoleMonster.2.0.0.nupkg", 1.2, StatusKind.Pass, "Successfully created package 'Artefacts\\ProjectBuild\\ConsoleMonster.2.0.0.nupkg'."),
new("Package", "zip", "Create module archive", 0.8, StatusKind.Pass, "Artefacts\\Packed\\ConsoleMonster-v2.0.0.zip (1.1 MB)"),
new("Publish", "publish", "Publish to PowerShell Gallery", 0.3, StatusKind.Skip, "RunMode is Build; publishing is disabled"),
};
string[] phaseNames = { "Prepare", "Restore", "Build", "Test", "Docs", "Sign", "Package", "Publish" };
string[] compilerNames = { "netstandard2.0", "net472", "net8.0", "PowerShell" };
int[] checkpoints = { 0, 2, 3, 5, 7, 9, 10, 12, 14, 16 };
int stepAt = Array.IndexOf(args, "--step");
int step = stepAt < 0 ? 0 : int.Parse(args[stepAt + 1], CultureInfo.InvariantCulture);
if (step < 0 || step >= checkpoints.Length) throw new ArgumentOutOfRangeException(nameof(step));
bool snapshot = args.Contains("--snapshot") || (!args.Contains("--interactive") && Console.IsInputRedirected);
int completed = snapshot ? checkpoints[step] : 0;
string PathText(string text) => text.Replace('\\', Path.DirectorySeparatorChar);
string Duration(double seconds) => seconds.ToString("0.0", CultureInfo.InvariantCulture) + "s";
StatusKind Worst(IEnumerable<BuildStep> items) => items.Any(item => item.Result == StatusKind.Warn) ? StatusKind.Warn
: items.All(item => item.Result == StatusKind.Skip) ? StatusKind.Skip : StatusKind.Pass;
// Layout: a phase strip and detailed checklist beside module facts, overall progress and compiler lanes.
Phases phases = new Phases();
foreach (string phase in phaseNames) {
BuildStep[] group = steps.Where(item => item.Phase == phase).ToArray();
int first = Array.IndexOf(steps, group[0]);
int last = first + group.Length;
StatusKind state = last <= completed ? Worst(group) : first <= completed && completed < steps.Length ? StatusKind.Running : StatusKind.Pending;
phases.Set(phase, state, detail: last <= completed ? Duration(group.Sum(item => item.Seconds)) : null);
}
StatusList status = new StatusList("steps");
for (int i = 0; i < steps.Length; i++) status.Set(steps[i].Key, PathText(steps[i].Label),
i < completed ? steps[i].Result : i == completed ? StatusKind.Running : StatusKind.Pending,
i < completed ? Duration(steps[i].Seconds) : null);
KeyValueList module = new KeyValueList("module").Add("Module", "[bold]ConsoleMonster[/]")
.Add("Version", "0.0.4 [muted]→[/] [bold]2.0.0[/]").Add("Frameworks", "netstandard2.0 · net472 · net8.0")
.Add("PowerShell", "5.1 Desktop · 7.x Core").Add("Run mode", "Build")
.Add("Elapsed", completed == 0 ? "-" : Duration(steps.Take(completed).Sum(item => item.Seconds)));
ProgressBar progress = new ProgressBar("Overall", "overall") { Total = steps.Length, Value = completed, LabelWidth = 8 };
Lanes compilers = new Lanes("compilers");
for (int i = 0; i < compilerNames.Length; i++) {
Lane lane = compilers.Add("c" + i, compilerNames[i], 1);
lane.Status = completed > i + 3 ? StatusKind.Pass : completed == i + 3 ? StatusKind.Running : StatusKind.Pending;
lane.Done = completed > i + 3 ? 1 : 0;
lane.Passed = (int)lane.Done;
lane.Detail = lane.Done > 0 ? "compiled" : lane.Status == StatusKind.Running ? "compiling" : "queued";
}
LogRegion log = new LogRegion { PreferredHeight = 8, ShowTime = false };
log.Write("Build engine · release pipeline");
foreach (BuildStep item in steps.Take(completed)) foreach (string line in item.Output) log.Write(PathText(line), item.Result == StatusKind.Warn ? "warn" : item.Result == StatusKind.Skip ? "skip" : "info");
if (completed < steps.Length) log.Write(PathText(steps[completed].Label) + "...");
Rows body = new Rows(new Padding(phases, 1, 0, 1, 1),
new Columns().Add(new Panel(status, "Steps"), LayoutSize.Fraction(1.5))
.Add(new Panel(new Rows(module, new Rule(), progress, new Rule("Compiler workers"), compilers), "Module")))
.Add(new Panel(log, "Output"), LayoutSize.Fraction());
Dashboard dashboard = new Dashboard("[bold]Module build[/]", body) {
Subtitle = "Build-Module ConsoleMonster", ShowClock = false,
Footer = "[muted]Prepare · Restore · Build · Test · Docs · Sign · Package · Publish Ctrl+C stop[/]"
};
// Behaviour: native handles update each region while ordinary tool output is captured by the live session.
if (snapshot) { Terminal.Write(dashboard); return; }
using LiveSession session = new LiveSession(dashboard);
session.Run(live => {
var phaseHandle = phases.Handle(live);
var stepHandle = status.Handle(live);
var moduleHandle = module.Handle(live);
var progressHandle = progress.Handle(live);
var compilerHandle = compilers.Handle(live);
double elapsed = 0;
for (int i = 0; i < steps.Length; i++) {
if (live.Cancellation.IsCancellationRequested) break;
BuildStep item = steps[i];
phaseHandle.Start(item.Phase);
stepHandle.Set(item.Key, StatusKind.Running);
progressHandle.Label(item.Phase);
if (i >= 3 && i <= 6) compilerHandle.Start("c" + (i - 3), "compiling");
Console.WriteLine(PathText(item.Label) + "...");
if (live.Cancellation.WaitHandle.WaitOne((int)(item.Seconds * 400))) break;
foreach (string line in item.Output) {
if (item.Result == StatusKind.Warn) Console.Error.WriteLine(PathText(line)); else Console.WriteLine(PathText(line));
}
elapsed += item.Seconds;
stepHandle.Set(item.Key, item.Result);
stepHandle.Detail(item.Key, Duration(item.Seconds));
moduleHandle.Set("Elapsed", Duration(elapsed));
progressHandle.Set(i + 1);
if (i >= 3 && i <= 6) {
compilerHandle.Advance("c" + (i - 3), StatusKind.Pass, "compiled");
compilerHandle.Complete("c" + (i - 3), StatusKind.Pass);
}
if (i == steps.Length - 1 || steps[i + 1].Phase != item.Phase) {
var group = steps.Where(value => value.Phase == item.Phase);
live.Post(new LiveEvent("phases", "state", item.Phase, Duration(group.Sum(value => value.Seconds)), Worst(group).ToString()));
}
}
});
record BuildStep(string Phase, string Key, string Label, double Seconds, StatusKind Result, params string[] Output);