using System.Text.Json; using System.Text.Json.Serialization; namespace w4c_workflows.Services.Execution; /// /// Shared helpers for the subprocess-backed executors: environment construction, /// working-directory resolution and the stdout → JSON output interpretation. /// /// Output contract: a program signals its result by printing one JSON value on /// the last non-empty line of stdout. If that line is not valid JSON the whole /// (trimmed) stdout is wrapped as a JSON string instead, so every output is /// storable JSON. /// internal static class ExecutionHelpers { private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web) { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; public static string ResolveWorkingDir(TaskInvocation invocation) => string.IsNullOrWhiteSpace(invocation.WorkingDir) ? Directory.GetCurrentDirectory() : Path.GetFullPath(invocation.WorkingDir); /// WF_* runtime context + the workflow/task-declared env, merged. public static IReadOnlyDictionary BuildEnvironment(TaskInvocation invocation) { var env = new Dictionary(StringComparer.Ordinal) { ["WF_TENANT_ID"] = invocation.TenantId, ["WF_RUN_ID"] = invocation.RunId, ["WF_TASK_ID"] = invocation.TaskId, ["WF_TASK_KEY"] = invocation.TaskKey, ["WF_INPUT"] = invocation.Input ?? "{}", }; foreach (var (key, value) in invocation.Env) env[key] = value; return env; } public static ExecutionResult ToResult(ProcessOutput output, TimeSpan duration) { var success = output.ExitCode == 0; var error = success ? null : LastNonEmptyLine(output.Stderr) ?? $"exit code {output.ExitCode}"; return new ExecutionResult( success, output.ExitCode, output.Stdout, output.Stderr, success ? ParseOutput(output.Stdout) : null, error, duration); } public static string? LastNonEmptyLine(string? text) { if (string.IsNullOrEmpty(text)) return null; foreach (var line in text.Split('\n')) { var trimmed = line.Trim('\r', ' ', '\t'); if (trimmed.Length > 0) return trimmed; } return null; } private static string? ParseOutput(string stdout) { var line = LastNonEmptyLine(stdout); if (line == null) return null; try { using var _ = JsonDocument.Parse(line); return line; // already valid JSON — store it verbatim } catch (JsonException) { return JsonSerializer.Serialize(stdout.Trim(), Json); } } }