diff --git a/Directory.Packages.props b/Directory.Packages.props
index 07e8ce5..64ad5b4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,6 +16,8 @@
+
+
diff --git a/Source/Cli.Specs/Cli.Specs.csproj b/Source/Cli.Specs/Cli.Specs.csproj
index 5940b37..147ca70 100644
--- a/Source/Cli.Specs/Cli.Specs.csproj
+++ b/Source/Cli.Specs/Cli.Specs.csproj
@@ -13,6 +13,8 @@
+
+
diff --git a/Source/Cli.Specs/Usings.cs b/Source/Cli.Specs/Usings.cs
index 9e3f830..c5cbe4c 100644
--- a/Source/Cli.Specs/Usings.cs
+++ b/Source/Cli.Specs/Usings.cs
@@ -9,6 +9,7 @@
global using Cratis.Cli.Commands.Init;
global using Cratis.Cli.Commands.Llm;
global using Cratis.Cli.Commands.Prologue;
+global using Cratis.Cli.Commands.Render;
global using Cratis.Cli.Commands.Run;
global using Cratis.Cli.Commands.Screenplay;
global using Cratis.Specifications;
diff --git a/Source/Cli.Specs/for_RenderCommand/given/a_render_command.cs b/Source/Cli.Specs/for_RenderCommand/given/a_render_command.cs
new file mode 100644
index 0000000..a96555f
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/given/a_render_command.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.given;
+
+///
+/// Base context that puts a document in a temporary folder and substitutes the rendering.
+///
+public class a_render_command : Specification
+{
+ protected string _folder;
+ protected string _document;
+ protected string _previousDirectory;
+ protected IScreenplayRendering _rendering;
+ protected RenderCommand _command;
+ protected RenderSettings _settings;
+
+ void Establish()
+ {
+ var created = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())).FullName;
+ _previousDirectory = Directory.GetCurrentDirectory();
+ Directory.SetCurrentDirectory(created);
+
+ // Read the folder back so that specs compare against the same fully resolved path the command sees —
+ // the temp folder is reached through a symbolic link on macOS.
+ _folder = Directory.GetCurrentDirectory();
+ _document = Path.Combine(_folder, "MyApp.play");
+ File.WriteAllText(_document, "domain Library\n");
+
+ _rendering = Substitute.For();
+ _rendering.Render(Arg.Any(), Arg.Any()).Returns(new RenderedScreenplay(1, [], []));
+
+ _command = new RenderCommand(_rendering);
+ _settings = new RenderSettings { Output = OutputFormats.JsonCompact };
+ }
+
+ ///
+ /// Executes the command with the established settings.
+ ///
+ /// The exit code.
+ protected Task Execute() =>
+ ((ICommand)_command).ExecuteAsync(
+ new CommandContext([], Substitute.For(), "render", null),
+ _settings,
+ CancellationToken.None);
+
+ void Destroy()
+ {
+ Directory.SetCurrentDirectory(_previousDirectory);
+
+ if (Directory.Exists(_folder))
+ {
+ Directory.Delete(_folder, true);
+ }
+ }
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_an_output_directory_is_given.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_an_output_directory_is_given.cs
new file mode 100644
index 0000000..a9ed121
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_an_output_directory_is_given.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+[Collection(CliSpecsCollection.Name)]
+public class and_an_output_directory_is_given : given.a_render_command
+{
+ int _result;
+
+ void Establish()
+ {
+ _settings.Path = "MyApp.play";
+ _settings.Target = "src/MyApp";
+ }
+
+ async Task Because() => _result = await Execute();
+
+ [Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
+ [Fact] void should_render_into_it() => _rendering.Received(1).Render(_document, Path.Combine(_folder, "src", "MyApp"));
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_no_documents_are_found.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_no_documents_are_found.cs
new file mode 100644
index 0000000..722bc74
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_no_documents_are_found.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+[Collection(CliSpecsCollection.Name)]
+public class and_no_documents_are_found : given.a_render_command
+{
+ int _result;
+
+ void Establish() => _rendering.Render(Arg.Any(), Arg.Any()).Returns(new RenderedScreenplay(0, [], []));
+
+ async Task Because() => _result = await Execute();
+
+ [Fact] void should_report_that_nothing_was_found() => _result.ShouldEqual(ExitCodes.NotFound);
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_application_cannot_carry_everything.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_application_cannot_carry_everything.cs
new file mode 100644
index 0000000..9172aa5
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_application_cannot_carry_everything.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+///
+/// A Screenplay document states more than any one target can express. What does not survive the crossing is
+/// reported rather than dropped silently — and it is not a failure, so the command still succeeds.
+///
+[Collection(CliSpecsCollection.Name)]
+public class and_the_application_cannot_carry_everything : given.a_render_command
+{
+ int _result;
+ string _error;
+ TextWriter _previousError;
+ StringWriter _capturedError;
+
+ void Establish()
+ {
+ _previousError = Console.Error;
+ _capturedError = new StringWriter();
+ Console.SetError(_capturedError);
+
+ _rendering
+ .Render(Arg.Any(), Arg.Any())
+ .Returns(new RenderedScreenplay(1, [], ["Slice 'Register' declares 2 screen declaration(s) with no rendered equivalent"]));
+ }
+
+ async Task Because()
+ {
+ _result = await Execute();
+ _error = _capturedError.ToString();
+ }
+
+ [Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
+ [Fact] void should_say_what_the_rendered_application_does_not_carry() =>
+ _error.ShouldContain("Slice 'Register' declares 2 screen declaration(s) with no rendered equivalent");
+
+ void Destroy() => Console.SetError(_previousError);
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_has_an_error.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_has_an_error.cs
new file mode 100644
index 0000000..bab8293
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_has_an_error.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+[Collection(CliSpecsCollection.Name)]
+public class and_the_document_has_an_error : given.a_render_command
+{
+ int _result;
+
+ void Establish() =>
+ _rendering
+ .Render(Arg.Any(), Arg.Any())
+ .Returns(new RenderedScreenplay(
+ 1,
+ [new ScreenplayDiagnostic(ScreenplayDiagnosticSeverity.Error, "PLAY0001", "an error", "MyApp.play(3,1)")],
+ []));
+
+ async Task Because() => _result = await Execute();
+
+ [Fact] void should_report_a_validation_error() => _result.ShouldEqual(ExitCodes.ValidationError);
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_renders.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_renders.cs
new file mode 100644
index 0000000..1d147ed
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_document_renders.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+[Collection(CliSpecsCollection.Name)]
+public class and_the_document_renders : given.a_render_command
+{
+ int _result;
+
+ void Establish() => _settings.Path = "MyApp.play";
+
+ async Task Because() => _result = await Execute();
+
+ [Fact] void should_succeed() => _result.ShouldEqual(ExitCodes.Success);
+ [Fact] void should_render_the_resolved_document() =>
+ _rendering.Received(1).Render(_document, Path.Combine(_folder, RenderCommand.DefaultTarget));
+}
diff --git a/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_path_cannot_be_resolved.cs b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_path_cannot_be_resolved.cs
new file mode 100644
index 0000000..e4ec5ab
--- /dev/null
+++ b/Source/Cli.Specs/for_RenderCommand/when_rendering/and_the_path_cannot_be_resolved.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.for_RenderCommand.when_rendering;
+
+[Collection(CliSpecsCollection.Name)]
+public class and_the_path_cannot_be_resolved : given.a_render_command
+{
+ int _result;
+
+ void Establish() => _settings.Path = "Missing/MyApp.play";
+
+ async Task Because() => _result = await Execute();
+
+ [Fact] void should_report_that_it_was_not_found() => _result.ShouldEqual(ExitCodes.NotFound);
+ [Fact] void should_not_render_anything() => _rendering.DidNotReceive().Render(Arg.Any(), Arg.Any());
+}
diff --git a/Source/Cli/Cli.csproj b/Source/Cli/Cli.csproj
index 33ec078..b9f5894 100644
--- a/Source/Cli/Cli.csproj
+++ b/Source/Cli/Cli.csproj
@@ -39,6 +39,9 @@
+
+
+
diff --git a/Source/Cli/Commands/Render/IScreenplayRendering.cs b/Source/Cli/Commands/Render/IScreenplayRendering.cs
new file mode 100644
index 0000000..974bd54
--- /dev/null
+++ b/Source/Cli/Commands/Render/IScreenplayRendering.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Cratis.Cli.Commands.Screenplay;
+
+namespace Cratis.Cli.Commands.Render;
+
+///
+/// Defines a system that renders Screenplay documents into a working application.
+///
+///
+/// The seam between the CLI and the Stage renderer, mirroring on the other
+/// arrow. Everything the CLI does around rendering — resolving the path, reporting, deciding the exit code — is
+/// expressed against this interface so it stays independent of which target is rendered into.
+///
+public interface IScreenplayRendering
+{
+ ///
+ /// Renders the Screenplay document, or every document beneath the folder, into the target directory.
+ ///
+ /// The full path of a .play file, or of a folder to search.
+ /// The full path of the directory to render into.
+ /// The holding what was rendered and what could not be.
+ Task Render(string targetPath, string outputDirectory);
+}
diff --git a/Source/Cli/Commands/Render/RenderCommand.cs b/Source/Cli/Commands/Render/RenderCommand.cs
new file mode 100644
index 0000000..1be388b
--- /dev/null
+++ b/Source/Cli/Commands/Render/RenderCommand.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Cratis.Cli.Commands.Screenplay;
+
+namespace Cratis.Cli.Commands.Render;
+
+///
+/// Renders Screenplay (.play) documents into a working Cratis application.
+///
+///
+/// A sibling of run rather than a mode of it: run hands a folder to a container and watches, while
+/// this materializes the application as source on disk and exits. They share only how a document is found.
+///
+[LlmDescription("Renders Cratis Screenplay (.play) documents into a Cratis application on disk. Takes a .play file, or a folder in which case every .play file beneath it is rendered into one application. Nothing needs to be running. The target project is scaffolded on first use. What the document states but the rendered application cannot express is reported to standard error rather than dropped silently; the command still succeeds. A document the compiler rejects is not rendered at all.")]
+[CliCommand("render", "Render Screenplay (.play) documents into a Cratis application")]
+[CliExample("render")]
+[CliExample("render", "./MyApp.play")]
+[CliExample("render", "./plays", "--target", "./src/MyApp")]
+[LlmOption("[PATH]", "string", "Screenplay (.play) file, or folder to render every .play file beneath. Defaults to the current directory.")]
+[LlmOption("--target", "string", "Directory to render the application into (default: ./out).")]
+[LlmOutputAdvice("json-compact", "The summary goes to standard output and what could not be rendered to standard error; json-compact makes both machine-readable.")]
+public class RenderCommand : AsyncCommand
+{
+ ///
+ /// The directory rendered into when none is given.
+ ///
+ public const string DefaultTarget = "out";
+
+ readonly IScreenplayRendering _rendering;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RenderCommand()
+ : this(new ScreenplayRendering())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The rendering to render the documents with.
+ internal RenderCommand(IScreenplayRendering rendering)
+ {
+ _rendering = rendering;
+ }
+
+ ///
+ protected override async Task ExecuteAsync(CommandContext context, RenderSettings settings, CancellationToken cancellationToken)
+ {
+ var format = settings.ResolveOutputFormat();
+ var currentDirectory = Directory.GetCurrentDirectory();
+
+ var resolved = PlayFileTargetResolver.Resolve(settings.Path, currentDirectory);
+ if (!resolved.IsResolved)
+ {
+ OutputFormatter.WriteError(format, resolved.Error!, resolved.Suggestion, ExitCodes.NotFoundCode);
+ return ExitCodes.NotFound;
+ }
+
+ var target = Path.GetFullPath(settings.Target ?? DefaultTarget, currentDirectory);
+ var rendered = await _rendering.Render(resolved.Path!, target);
+
+ if (rendered.Documents == 0)
+ {
+ // Silently succeeding on a folder holding nothing turns the command into a no-op in CI, which is
+ // exactly where it is trusted the most.
+ OutputFormatter.WriteError(
+ format,
+ $"No Screenplay ({PlayFileTargetResolver.Extension}) files found in '{resolved.Path}'",
+ $"Point the command at a {PlayFileTargetResolver.Extension} file, or at a folder holding one",
+ ExitCodes.NotFoundCode);
+ return ExitCodes.NotFound;
+ }
+
+ ScreenplayDiagnosticsWriter.Write(format, rendered.Diagnostics);
+
+ var exitCode = ScreenplayDiagnostics.ExitCodeFor(rendered.Diagnostics);
+ if (exitCode != ExitCodes.Success)
+ {
+ var errors = rendered.Diagnostics.Count(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error);
+ OutputFormatter.WriteError(
+ format,
+ $"Nothing was rendered — the document reported {errors} error(s)",
+ "Fix the reported errors in the Screenplay document, then render again",
+ ExitCodes.ValidationErrorCode);
+ return exitCode;
+ }
+
+ WriteReported(rendered);
+ WriteResult(format, target, rendered);
+ return ExitCodes.Success;
+ }
+
+ ///
+ /// Writes what the rendered application does not carry. This is not a failure — a Screenplay document states
+ /// more than any one target expresses — but it is the difference between the document and what was produced,
+ /// so it goes to standard error where it will be read rather than into a summary line.
+ ///
+ /// What rendering produced.
+ static void WriteReported(RenderedScreenplay rendered)
+ {
+ foreach (var reported in rendered.Reported)
+ {
+ Console.Error.WriteLine(reported);
+ }
+ }
+
+ static void WriteResult(string format, string targetDirectory, RenderedScreenplay rendered)
+ {
+ if (string.Equals(format, OutputFormats.Quiet, StringComparison.Ordinal))
+ {
+ Console.WriteLine(targetDirectory);
+ return;
+ }
+
+ OutputFormatter.WriteObject(
+ format,
+ new
+ {
+ Target = targetDirectory,
+ rendered.Documents,
+ NotCarried = rendered.Reported.Count
+ },
+ result =>
+ {
+ var content = new Markup(
+ $"[bold]{result.Target.EscapeMarkup()}[/]\n" +
+ $"Documents: {result.Documents}\n" +
+ $"Not carried: {result.NotCarried}");
+ var panel = new Panel(content)
+ .Header(" Rendered ")
+ .Border(BoxBorder.Rounded)
+ .BorderStyle(new Style(OutputFormatter.Success))
+ .Padding(1, 0);
+
+ AnsiConsole.WriteLine();
+ AnsiConsole.Write(panel);
+ });
+ }
+}
diff --git a/Source/Cli/Commands/Render/RenderSettings.cs b/Source/Cli/Commands/Render/RenderSettings.cs
new file mode 100644
index 0000000..82214fb
--- /dev/null
+++ b/Source/Cli/Commands/Render/RenderSettings.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Cratis.Cli.Commands.Render;
+
+///
+/// Settings for the render command.
+///
+public class RenderSettings : GlobalSettings
+{
+ ///
+ /// Gets or sets the document or folder to render.
+ ///
+ [CommandArgument(0, "[PATH]")]
+ [Description("Screenplay (.play) file, or folder to render every .play file beneath. Defaults to the current directory.")]
+ public string? Path { get; set; }
+
+ ///
+ /// Gets or sets the directory to render into.
+ ///
+ ///
+ /// Named --target rather than --output because every command already carries a global
+ /// -o|--output for the output format, and it is the renderer's own word for where it writes.
+ ///
+ [CommandOption("--target ")]
+ [Description("Directory to render the application into. Defaults to './out'.")]
+ public string? Target { get; set; }
+}
diff --git a/Source/Cli/Commands/Render/RenderedScreenplay.cs b/Source/Cli/Commands/Render/RenderedScreenplay.cs
new file mode 100644
index 0000000..71e2e8e
--- /dev/null
+++ b/Source/Cli/Commands/Render/RenderedScreenplay.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Cratis.Cli.Commands.Screenplay;
+
+namespace Cratis.Cli.Commands.Render;
+
+///
+/// Represents what rendering a set of Screenplay documents produced.
+///
+/// How many documents were rendered.
+/// Everything the compiler reported about the documents.
+///
+/// Everything the renderer could not carry into the rendered application, in the order it was reported.
+///
+///
+/// is the point of the command as much as the files are. A Screenplay document states more
+/// than any one target can express, and the promise is that whatever does not survive the crossing is said out
+/// loud rather than quietly left behind — so it is carried back as a result, not written past the user.
+///
+public record RenderedScreenplay(int Documents, IReadOnlyList Diagnostics, IReadOnlyList Reported);
diff --git a/Source/Cli/Commands/Render/ScreenplayRendering.cs b/Source/Cli/Commands/Render/ScreenplayRendering.cs
new file mode 100644
index 0000000..ba6897f
--- /dev/null
+++ b/Source/Cli/Commands/Render/ScreenplayRendering.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Cratis. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Cratis.Cli.Commands.Screenplay;
+using Cratis.Stage.Contracts.Rendering;
+using Cratis.Stage.Rendering.Cratis;
+
+namespace Cratis.Cli.Commands.Render;
+
+///
+/// Renders Screenplay documents into a Cratis application with the Stage renderer.
+///
+///
+/// This is the only place in the CLI that knows the renderer exists, the way
+/// is the only place that knows the compiler does.
+///
+/// Compiles the documents and reports what the compiler found.
+/// Renders the compiled applications.
+public sealed class ScreenplayRendering(IScreenplayValidation validation, IRenderer renderer) : IScreenplayRendering
+{
+ ///
+ /// Initializes a new instance of the class with the default compiler and the
+ /// Cratis renderer.
+ ///
+ public ScreenplayRendering()
+ : this(new ScreenplayValidation(), CratisRenderer.CreateDefault())
+ {
+ }
+
+ ///
+ public async Task Render(string targetPath, string outputDirectory)
+ {
+ var compiled = validation.Validate(targetPath);
+ var diagnostics = compiled.Diagnostics;
+
+ // A document the compiler rejected has no application to render, and rendering the ones beside it would
+ // produce an application missing whatever the rejected document declared - without saying which parts.
+ if (diagnostics.Any(diagnostic => diagnostic.Severity == ScreenplayDiagnosticSeverity.Error))
+ {
+ return new(compiled.FileCount, diagnostics, []);
+ }
+
+ var output = new StringWriter();
+ var error = new StringWriter();
+
+ await renderer.Render(compiled.Applications, new DirectoryInfo(outputDirectory), output, error);
+
+ return new(compiled.FileCount, diagnostics, Lines(error));
+ }
+
+ static IReadOnlyList Lines(StringWriter writer) =>
+ [.. writer.ToString().Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
+}
diff --git a/Source/Cli/Commands/Screenplay/ScreenplayValidation.cs b/Source/Cli/Commands/Screenplay/ScreenplayValidation.cs
index 8be5eb2..b4777cc 100644
--- a/Source/Cli/Commands/Screenplay/ScreenplayValidation.cs
+++ b/Source/Cli/Commands/Screenplay/ScreenplayValidation.cs
@@ -4,6 +4,7 @@
using Cratis.Screenplay;
using Cratis.Screenplay.Diagnostics;
using Cratis.Screenplay.Files;
+using Cratis.Screenplay.Syntax;
namespace Cratis.Cli.Commands.Screenplay;
@@ -36,7 +37,10 @@ public ValidatedScreenplay Validate(string targetPath)
return new(
compilations.Length,
- [.. compilations.SelectMany(compilation => compilation.Result.Diagnostics.Select(diagnostic => Map(compilation.File, diagnostic)))]);
+ [.. compilations.SelectMany(compilation => compilation.Result.Diagnostics.Select(diagnostic => Map(compilation.File, diagnostic)))])
+ {
+ Applications = [.. compilations.Select(compilation => compilation.Result.Value).OfType()]
+ };
}
///
diff --git a/Source/Cli/Commands/Screenplay/ValidatedScreenplay.cs b/Source/Cli/Commands/Screenplay/ValidatedScreenplay.cs
index 3c1cab0..581acc2 100644
--- a/Source/Cli/Commands/Screenplay/ValidatedScreenplay.cs
+++ b/Source/Cli/Commands/Screenplay/ValidatedScreenplay.cs
@@ -1,6 +1,8 @@
// Copyright (c) Cratis. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+using Cratis.Screenplay.Syntax;
+
namespace Cratis.Cli.Commands.Screenplay;
///
@@ -8,4 +10,11 @@ namespace Cratis.Cli.Commands.Screenplay;
///
/// The number of .play files that were compiled.
/// Everything the compiler reported, across every file.
-public record ValidatedScreenplay(int FileCount, IReadOnlyList Diagnostics);
+public record ValidatedScreenplay(int FileCount, IReadOnlyList Diagnostics)
+{
+ ///
+ /// Gets the applications the compiler produced — one per document it could compile, and none for a document
+ /// it rejected. Validation only counts them; rendering is what needs them.
+ ///
+ public IReadOnlyList Applications { get; init; } = [];
+}