Generation/SuiGenerationPipeline.cs
using SboxUiDesigner.Runtime;
namespace SboxUiDesigner.Generation;
/// <summary>
/// Glue that runs validator → razor generator → scss generator → packs the
/// result. Pure logic; the file-system writer is a separate concern (M12).
///
/// Pipeline order:
/// 1. Validate the document via SuiDocumentValidator (schema invariants).
/// Bail with errors if invalid.
/// 2. Resolve class name + namespace from context (defaults from doc.Output).
/// 3. Emit Razor markup.
/// 4. Emit SCSS rules.
/// 5. Pack into SuiGenerationResult with kind+path+content+sha256 per file.
///
/// The path strings inside the result are project-relative; M12 will resolve
/// them against the actual filesystem.
/// </summary>
public static class SuiGenerationPipeline
{
public static SuiGenerationResult Run( SuiGenerationContext ctx )
{
var result = new SuiGenerationResult();
var doc = ctx?.Document;
if ( doc == null )
{
result.Errors.Add( "pipeline: context or document is null" );
return result;
}
// Schema validation — abort if invalid (cycles, dup ids, missing root).
var schemaReport = SuiDocumentValidator.Validate( doc );
foreach ( var w in schemaReport.Errors ) result.Errors.Add( $"validator: {w}" );
foreach ( var w in schemaReport.Warnings ) result.Warnings.Add( $"validator: {w}" );
if ( !schemaReport.IsValid ) return result;
// Resolve class/namespace defaults.
var rawClass = !string.IsNullOrEmpty( ctx.ClassName ) ? ctx.ClassName
: !string.IsNullOrEmpty( doc.Output?.ClassName ) ? doc.Output.ClassName
: doc.Name;
ctx.ClassName = SuiNameSanitizer.ToCSharpIdentifier( rawClass );
ctx.Namespace = !string.IsNullOrEmpty( ctx.Namespace ) ? ctx.Namespace
: !string.IsNullOrEmpty( doc.Output?.Namespace ) ? doc.Output.Namespace
: "Game.UI";
// Preview mode emits into a sentinel sub-namespace so the preview cache
// type never collides with the final compile output. Without this, a
// document that has been compiled to disk (final mode → namespace X)
// AND has a live preview cache (Code/_sui_preview/.../*.razor → also
// namespace X) yields TWO `partial class <Name>` declarations and
// the engine errors out with CS0111 (duplicate render-tree members).
if ( ctx.Mode == SuiGenerationMode.Preview && !ctx.Namespace.EndsWith( ".SuiPreview" ) )
{
ctx.Namespace = ctx.Namespace + ".SuiPreview";
}
var folder = (ctx.OutputFolder ?? "").Trim( '/', '\\', ' ' );
string Combine( string filename ) => string.IsNullOrEmpty( folder ) ? filename : folder + "/" + filename;
// Razor
var razor = new SuiRazorGenerator().Generate( ctx, result );
if ( !string.IsNullOrEmpty( razor ) )
result.AddFile( SuiGeneratedFileKind.Razor, Combine( $"{ctx.ClassName}.razor" ), razor );
// SCSS
var scss = new SuiScssGenerator().Generate( ctx, result );
if ( !string.IsNullOrEmpty( scss ) )
result.AddFile( SuiGeneratedFileKind.Scss, Combine( $"{ctx.ClassName}.razor.scss" ), scss );
return result;
}
}