acdream/src/AcDream.Headless/HeadlessEntryPoint.cs
Erik 2ff1e1228c fix(headless): S6 thread standardOutputIsTerminal instead of reading Console inside HeadlessProcessHost
HeadlessProcessHost read System.Console.IsOutputRedirected directly to
pick the console renderer's color mode, which only Program.cs (the
executable's own entry point) should ever touch -- the same reasoning
that already put the stdin probe there. Resolve
standardOutputIsTerminal next to the existing !Console.IsInputRedirected
probe in Program.cs and thread it through HeadlessEntryPoint.Run into
HeadlessProcessHost's constructor as a plain parameter.

StandardOutputIsTerminalParameterControlsColorNotTheRealConsole was
shown to fail with the parameter still unused (useColor still reading
the real Console.IsOutputRedirected, which the test host always
redirects): the standardOutputIsTerminal:true case expected dimmed
lifecycle output but got none, since the real console read forced
useColor=false regardless of what the test passed in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 08:05:37 +02:00

138 lines
4.8 KiB
C#

using AcDream.Headless.Configuration;
using AcDream.Headless.Credentials;
using AcDream.Headless.Hosting;
using AcDream.Headless.Platform;
namespace AcDream.Headless;
internal static class HeadlessEntryPoint
{
private const string HelpText =
"""
acdream-headless
Usage:
acdream-headless --help
acdream-headless validate --config <path> [path overrides]
acdream-headless run --config <path> [path overrides] [credentials]
Commands:
validate Validate a versioned headless configuration without connecting.
run Run exactly one configured no-window session.
Path overrides:
--config-dir <path>
--data-dir <path>
--cache-dir <path>
Direct single-session credentials:
-user <account> -password <password>
--user <account> --password <password>
""";
internal static int Run(
IReadOnlyList<string> arguments,
TextWriter output,
TextWriter error) =>
Run(
arguments,
TextReader.Null,
output,
error,
CancellationToken.None);
internal static int Run(
IReadOnlyList<string> arguments,
TextReader standardInput,
TextWriter output,
TextWriter error,
CancellationToken cancellationToken,
bool standardInputIsTerminal = false,
bool standardOutputIsTerminal = false)
{
ArgumentNullException.ThrowIfNull(arguments);
ArgumentNullException.ThrowIfNull(standardInput);
ArgumentNullException.ThrowIfNull(output);
ArgumentNullException.ThrowIfNull(error);
if (arguments.Count == 0
|| IsHelp(arguments[0]))
{
output.WriteLine(HelpText);
return (int)HeadlessExitCode.Success;
}
try
{
HeadlessCommandLine commandLine =
HeadlessCommandLine.Parse(arguments);
HeadlessConfiguration configuration =
HeadlessConfigurationLoader.Load(
commandLine.ConfigurationPath);
HeadlessPathOverrides configuredPaths =
configuration.Process?.Paths
?? new HeadlessPathOverrides();
HeadlessPathSet paths = HeadlessPathSet.Resolve(
configuredPaths.Merge(commandLine.Paths));
if (commandLine.Command == "run")
{
bool consoleEnabled = HeadlessConsoleOptions.Resolve(
commandLine.ConsoleEnabled,
standardInputIsTerminal);
using var host = new HeadlessProcessHost(
configuration,
paths,
standardInput,
output,
directCredentials:
commandLine.DirectCredentials,
consoleEnabled: consoleEnabled,
standardOutputIsTerminal: standardOutputIsTerminal);
return (int)host.RunAsync(cancellationToken)
.GetAwaiter()
.GetResult();
}
output.WriteLine(
$"Configuration valid: version {configuration.Version}, "
+ $"{configuration.Sessions.Count} session(s).");
return (int)HeadlessExitCode.Success;
}
catch (HeadlessCommandLineException)
{
error.WriteLine("Invalid command. Run --help for usage.");
return (int)HeadlessExitCode.UsageError;
}
catch (System.Text.Json.JsonException)
{
error.WriteLine(
"Configuration invalid: the JSON document is invalid.");
return (int)HeadlessExitCode.ConfigurationError;
}
catch (Exception exception)
when (exception is IOException
or UnauthorizedAccessException
or ArgumentException
or NotSupportedException
or HeadlessConfigurationException)
{
error.WriteLine($"Configuration invalid: {exception.Message}");
return (int)HeadlessExitCode.ConfigurationError;
}
catch (HeadlessCredentialException exception)
{
error.WriteLine($"Credential unavailable: {exception.Message}");
return (int)HeadlessExitCode.CredentialError;
}
catch (Exception exception)
{
error.WriteLine(
$"Headless runtime failed: {exception.GetType().FullName}");
return (int)HeadlessExitCode.RuntimeError;
}
}
private static bool IsHelp(string argument) =>
string.Equals(argument, "--help", StringComparison.Ordinal)
|| string.Equals(argument, "-h", StringComparison.Ordinal);
}