Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

GitLab.Ci.Yaml: Schema-Driven Typed Pipelines for .NET

Feed it GitLab's own JSON Schema, and get fully typed C# models with fluent builders, YAML round-trip serialization, version-aware annotations across 11 releases, and a contributor pattern for modular pipeline composition. No more YAML typos, no more silent pipeline failures, no more guessing which keyword was added in which GitLab version.

GitLab.Ci.Yaml is a schema-driven code generation library that downloads the official GitLab CI JSON Schema, parses it at compile time using a Roslyn incremental source generator, and emits 30 typed C# models, 30 fluent builders, and version metadata — all from a single [GitLabCiBundle] attribute. Hand-written infrastructure handles YAML serialization, deserialization, version comparison, and modular pipeline composition.

The result: zero runtime reflection, full IntelliSense, compile-time safety, and multi-version awareness across 11 GitLab releases (18.0 through 18.10).


Part I: The Problem

Why hand-writing .gitlab-ci.yml is a minefield — silent typos that pass green, version drift across monthly GitLab releases, union types that defy static typing, a flat root structure where reserved keys and job names coexist, and a combinatorial explosion across 54 definition types.

Part II: High-Level Architecture

The four-project pattern — Attributes, Design, SourceGenerator, and Main Library — with dependency graphs, project responsibilities, and how the Design CLI feeds JSON schemas to the Roslyn incremental source generator that emits 61 generated files.

Part III: Schema Acquisition and JSON Schema Deep Dive

How the Design CLI downloads versioned schemas from GitLab's release API, the JSON Schema draft-07 format with its definitions, allOf, oneOf, anyOf, and $ref patterns, type alias mappings for 40+ definitions, and the schema adaptations that bridge the gap between JSON Schema and C#.

Part IV: The Source Generator Pipeline and SchemaReader

Inside GitLabCiBundleGenerator — the five-stage incremental generation pipeline. The SchemaReader with its two-pass parsing, ParseProperty handling of $ref/oneOf/anyOf/inline objects, the MapRefToType switch with 40+ mappings, and ParseOneOf union type resolution.

Part V: Multi-Version Schema Merging

How SchemaVersionMerger combines 11 GitLab schema versions into a unified API — sorting by semantic version, tracking first/last appearance per property, annotating with [SinceVersion]/[UntilVersion], and the UnifiedSchema data model.

Part VI: Code Emission — Models, Builders, Naming, and Version Metadata

ModelClassEmitter generating the root model and 30 definition classes with inline object recursion. BuilderHelper bridging schema properties to the BuilderEmitter. NamingHelper converting between snake_case and PascalCase. VersionMetadataEmitter producing version constants and attributes. Full generated code output shown.

Part VII: The Generated API Surface and Fluent Builder

All 30 generated model classes mapped to GitLab CI concepts. The complete GitLabCiFileBuilder with WithJob(), WithVariables(), nested callbacks, per-property validation, and Result<T>-based async building.

Part VIII: YAML Serialization and Deserialization

The hand-written GitLabCiYamlWriter with flat root dictionary merging. The two-phase GitLabCiYamlReader with re-serialize/re-deserialize job extraction. StringOrListConverter for string | List<string> unions. GitLabCiNamingConvention for PascalCase/snake_case bridging.

Part IX: Runtime API — Contributors, Versioning, and Testing

IGitLabCiContributor for modular pipeline composition with real-world examples (DotNet, Docker, Deploy). GitLabCiVersion record with semantic comparison operators. All 20 xUnit tests across 4 test classes — models, writer, reader, versions — with the test fixture.

Part X: Design Trade-offs, Project Configuration, and Architecture

Every conscious design choice explained — type aliases to primitives, List<object> for stages, hand-written reader/writer, no YAML anchors. All 5 .csproj files detailed. Comparison table vs hand-written YAML, templating, and other approaches. Full architecture tree.

Part XI: Advanced Patterns and Recipes

Six production-ready recipes — multi-environment deployment with manual gates, matrix testing across frameworks, conditional contributor composition, pipeline linting against organizational rules, pipeline migration from deprecated keywords, and pipeline diff tools.

Part XII: Deep Dives — Flat Root, Union Types, Builders, and YamlDotNet

Technical deep dives into the flat root model mechanics, union type handling strategies (5 approaches), builder pattern internals (AbstractBuilder, DictionaryBuilder, validation pipeline), incremental source generator performance, YamlDotNet integration details, cross-references with BinaryWrapper and DockerCompose.Bundle, update workflow, performance characteristics, and known limitations.

Part XIII: Real-World Scenarios, Pipelines at Scale, and Testing Strategies

Complete production scenarios — monorepo with 3 services generating 16 jobs, library release pipelines with multi-target testing, pipeline-as-code CLI generators, organization-wide security baselines with compliance validation, schema evolution analysis across versions, the Extensions dictionary pattern, compatibility reports, and comprehensive testing strategies for pipeline generators.

⬇ Download