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

The Design CLI

The whole pipeline starts with a few JSON files on disk. Where do they come from? Not from a hand-maintained copy — from a tiny .NET console app that pulls them from SchemaStore on demand. Here is the entire program (Bundle.Design/Program.cs):

using FrenchExDev.Net.Wrapper.Versioning;

var outputDir = Path.GetFullPath(Path.Combine(
    AppContext.BaseDirectory, "..", "..", "..", "..",
    "FrenchExDev.Net.Traefik.Bundle", "schemas"));

var schemas = new (string Name, string Url)[]
{
    ("static",        "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/traefik-v3.json"),
    ("file-provider", "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/traefik-v3-file-provider.json"),
};

var pipeline = new DesignPipeline<(string Name, string Url)>()
    .UseHttpDownload(item => item.Url)
    .UseSave()
    .Build();

return await new DesignPipelineRunner<(string Name, string Url)>
{
    ItemCollector = new StaticItemCollector<(string Name, string Url)>(schemas),
    Pipeline = pipeline,
    KeySelector = item => item.Name,
    OutputDir = outputDir,
    OutputFilePattern = "traefik-v3-{key}.json",
}.RunAsync(args);

That's it. dotnet run --project src/FrenchExDev.Net.Traefik.Bundle.Design writes:

src/FrenchExDev.Net.Traefik.Bundle/schemas/
├── traefik-v3-static.json
├── traefik-v3-file-provider.json
└── traefik-v3.1-file-provider.json   ← added by hand for the v3.1 test

This is intentionally a one-shot tool, not a build step. Schemas don't change every CI run. When Traefik publishes a new schema, a maintainer runs the Design CLI, eyeballs the diff, and commits the result. The build is reproducible because the schema files are in source control; the Design CLI is the human-driven refresh button.

DesignPipeline<T> and DesignPipelineRunner<T> come from FrenchExDev.Net.Wrapper.Versioning, a sibling utility in the monorepo that gives you UseHttpDownload / UseSave building blocks plus version-aware OutputFilePattern substitution. It's reused by every "go fetch the upstream schema" tool in the FrenchExDev stack.

The csproj wiring

Once the JSON files are on disk, the consuming Bundle project needs to do two things with them. First, hand them to the source generator at build time. Second, embed them in the assembly so the runtime serializer can validate against the same bytes.

Here is the relevant slice of FrenchExDev.Net.Traefik.Bundle.csproj:

<ItemGroup>
  <ProjectReference
      Include="..\FrenchExDev.Net.Traefik.Bundle.SourceGenerator\FrenchExDev.Net.Traefik.Bundle.SourceGenerator.csproj"
      OutputItemType="Analyzer"
      ReferenceOutputAssembly="false" />
  <ProjectReference
      Include="..\..\..\Builder\src\FrenchExDev.Net.Builder.SourceGenerator.Lib\FrenchExDev.Net.Builder.SourceGenerator.Lib.csproj"
      OutputItemType="Analyzer"
      ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
  <PackageReference Include="YamlDotNet" />
  <PackageReference Include="JsonSchema.Net" />
</ItemGroup>

<ItemGroup>
  <AdditionalFiles  Include="schemas\traefik-v*.json" />
  <EmbeddedResource Include="schemas\traefik-v*.json" />
</ItemGroup>

Three things matter here:

  1. OutputItemType="Analyzer" ReferenceOutputAssembly="false" — the source generator and the shared BuilderEmitter library are loaded into the Roslyn analyzer host, not added as runtime references. Roslyn can only load netstandard2.0 analyzers, and the runtime Bundle doesn't need either DLL once code is generated.
  2. <AdditionalFiles> — this is what makes the JSON files visible to IIncrementalGenerator via context.AdditionalTextsProvider. Without this line, the generator runs but sees no schemas, and reports the TFK004 diagnostic at compile time (see Part 7). The glob is traefik-v*.json so adding traefik-v3.1-file-provider.json to the folder is all you have to do — no .csproj edit.
  3. <EmbeddedResource> with the same glob — the runtime TraefikSerializer.LoadEmbeddedSchema() does an asm.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith(fileName)) lookup, so the build-time and runtime sides see the byte-identical schema files. One folder, one glob, two consumers. No drift possible.

Why this matters for the rest of the series

Everything from Part 3 onward assumes the generator can see traefik-v3-static.json, traefik-v3-file-provider.json, and traefik-v3.1-file-provider.json as AdditionalFile instances. The Design CLI is how they get there. The AdditionalFiles glob is how Roslyn finds them. The EmbeddedResource glob is how the runtime serializer loads them. Three lines of .csproj are the entire pipeline plumbing.

← Part 1: Why Strongly-Type Traefik? · Next: Part 3 — Reading JSON Schemas →

⬇ Download