A strongly-typed C# library for generating structured YAML text files, specifically GitHub Actions workflows, Dependabot configurations, and Azure DevOps Pipelines, using a type-safe model and typed expressions.
| Package | What it does |
|---|---|
| Invex.StructuredText | Core library with StructuredTextWriter and the expression API |
| Invex.StructuredText.GithubActions | GitHub Actions workflow and Dependabot config generation |
| Invex.StructuredText.AzureDevopsPipelines | Azure DevOps Pipelines YAML generation |
- Type-safe workflow authoring. Define CI/CD pipelines as C# objects with full IntelliSense and compile-time validation.
- Typed expressions. Write conditions with C# operators and values as interpolated strings, starting from one entry point,
Tex, and render them in each platform's syntax. - GitHub Actions. Generate workflows with triggers, jobs, steps, matrix strategies, permissions, concurrency, containers, and more.
- Dependabot. Generate
dependabot.ymlconfigurations with registries, update rules, groups, and schedules. - Azure DevOps Pipelines. Generate pipelines with stages, jobs, deployment strategies (runOnce, rolling, canary), resources, templates, and parameters.
- Deterministic output. The same model always produces the same text, so generated files diff cleanly.
dotnet add package Invex.StructuredText.GithubActions
# or
dotnet add package Invex.StructuredText.AzureDevopsPipelinesBoth platform packages depend on Invex.StructuredText core, so it will be installed automatically. All three packages
target net10.0. The platform packages also depend on the
Dunet source generator, which provides their discriminated-union model types.
using Invex.StructuredText.Expressions;
using Invex.StructuredText.GithubActions;
using Invex.StructuredText.GithubActions.GithubActionModel;
var workflow = new GithubAction
{
Name = "CI",
On =
[
new On.Push
{
Branches = ["main"],
BranchesIgnore = null,
Tags = null,
TagsIgnore = null,
Paths = null,
PathsIgnore = null,
},
],
Jobs =
[
new Job
{
Name = "build",
RunsOn = new() { Labels = ["ubuntu-latest"] },
Steps =
[
new Step.UsesStep { Name = "Checkout", Uses = "actions/checkout@v6" },
new Step.RunStep
{
Name = "Build",
If = Tex.GitHub.EventName.Eq("push"),
Run = [$"dotnet build -c Release -p:Version=1.0.{Tex.GitHub.RunNumber}"],
},
],
},
],
};
var writer = new GithubActionWriter();
writer.Write(workflow);
File.WriteAllText(".github/workflows/ci.yml", writer.TextWriter.ToString());Everything that creates an expression starts with Tex.: references, status checks, functions and the platform
contexts. Conditions use C# operators, and values are interpolated strings.
// if: (github.event_name == 'push' || startsWith(github.ref, 'refs/tags/')) && success()
If = (Tex.GitHub.EventName.Eq("push") || Tex.GitHub.Ref.StartsWith("refs/tags/")) && Tex.Success(),
// group: ${{ github.workflow }}-${{ github.ref }}
Group = $"{Tex.GitHub.Workflow}-{Tex.GitHub.Ref}",
// NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
["NUGET_API_KEY"] = Tex.Secret("NUGET_API_KEY"),
// version: ${{ needs.setup.outputs.version }}
["version"] = Tex.JobOutput("setup", "version"),The same expressions print in Azure DevOps syntax when used in a pipeline. See Expressions and the samples.
var pipeline = new DevopsPipeline.DevopsPipelineWithSteps
{
Trigger = new Trigger.BranchList { Branches = ["main"] },
Steps =
[
new Step.Script
{
ScriptContent = $"echo Building {Tex.DevOps.Build.SourceBranchName}",
Condition = Tex.DevOps.Build.Reason.Ne("PullRequest"),
},
],
};
var writer = new DevopsPipelineWriter();
writer.Write(pipeline);
var yaml = writer.TextWriter.ToString();var config = new DependabotConfig
{
Updates =
[
new DependabotUpdate
{
PackageEcosystem = PackageEcosystem.Nuget.ToYamlValue(),
Directory = "/",
Schedule = new() { Interval = ScheduleInterval.Weekly },
},
],
};
var writer = new DependabotConfigWriter();
writer.WriteConfig(config);
var yaml = writer.TextWriter.ToString();A small console project in your repository can regenerate every workflow file; commit the output and the diff shows
exactly what changed. Use a fresh writer per file, or call writer.TextWriter.Reset() between documents. See
Getting Started.
Full documentation: https://invex-games.github.io/structured-text/
| Guide | Contents |
|---|---|
| Introduction | What this library is, when to use it, and its key concepts |
| Getting Started | Install and generate your first file |
| Expressions | Conditions, references and interpolated values |
| StructuredTextWriter | The indentation-aware writer behind every generator |
| GitHub Actions Workflows | Workflow generation in depth |
| Dependabot Configuration | dependabot.yml generation |
| Azure DevOps Pipelines | Pipelines, stages, jobs, deployment strategies |
| Architecture | How the packages fit together and how to extend them |
| FAQ | Common questions and troubleshooting |
| API Reference | Generated reference for every public type |
The repository requires the .NET 10 SDK selected by global.json:
dotnet build Invex.StructuredText.slnx
dotnet test Invex.StructuredText.slnxBuild and preview the documentation site with the Atom build:
atom BuildDocs
atom ServeDocsSee AGENTS.md for repository structure, code conventions, and contributor guidance.
This project is licensed under the MIT License.