FTS.Platform.Templates 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet new install FTS.Platform.Templates@0.1.0
                    
This package contains a .NET Template Package you can call from the shell/command line.

FTS.Platform.Templates

Two dotnet new templates, so a new service starts already composed against the published FTS.Platform.* packages instead of being assembled by hand from a reference host.

dotnet new install FTS.Platform.Templates
dotnet new fts-api -n Ordering --db none --auth none
dotnet new fts-module -n Billing -o src/Modules/Billing

They are deliberately thin. Everything they emit is wiring: a composition root, a nuget.config, one example module to delete, and a test project that points FTS.Platform.ArchitectureRules at the solution it just created. There are no base classes, no helper types and no conventions folder — if something here looks reusable, it belongs in a package, and finding one is a finding to report rather than a file to generate.

The two templates are versioned independently of the platform. What ties them together is --platform-version, whose default is the platform's current release.


fts-api — the service

A project template. dotnet new fts-api -n Ordering writes:

Ordering.slnx                             two projects, nothing else
nuget.config                              <clear/>, the fts feed, nuget.org, no credential
src/Ordering/Ordering.csproj              only the packages this composition actually uses
src/Ordering/Program.cs                   the composition root: one registration per seam
src/Ordering/Example/                     the directory to delete - the layered example, six files
src/Ordering/Example/ExampleModule.cs       the module's composition root
src/Ordering/Example/Domain/Widget.cs       the entity that carries the rule
src/Ordering/Example/Application/           ArchiveWidget (IWriteHandler) and IWidgetStore
src/Ordering/Example/Infrastructure/        the in-memory store you replace
src/Ordering/Example/Api/                   two routes, both AllowAnonymous
src/Ordering/appsettings.json             the keys this composition needs, and no values it may not choose
tests/Ordering.Tests/Ordering.Tests.csproj
tests/Ordering.Tests/BoundaryTests.cs     the new solution enforcing its own boundaries
tests/Ordering.Tests/ExampleDomainTests.cs  the emitted rule, in memory - deleted with Example/

-n names the solution, the host project, its namespace-free root and the test project; -o decides where they land. With no flags at all you get the none composition below.

Parameters

Four of them are the four seams of ADR 0007, one flag each. dotnet new fts-api --help prints every default listed here.

Parameter Choices Default What the emitted Program.cs registers
--auth none, jwt none none: AnonymousCurrentActor as a Singleton. jwt: AuthOptions.Resolve as the host's first statement, then AddHttpContextAccessor(), AddTokenAuthentication(auth), UseAuthentication() and UseAuthorization().
--db none, sqlite none sqlite: SqliteConnectionFactory behind IDbConnectionFactory, SystemClock, InProcessEventPublisher, and DatabaseMigrator.Run after Build(). none: NullEventPublisher, and no factory, clock or migrator.
--tenancy fixed, claims fixed fixed: SingleTenantContext holding one constant TenantRefreplace both of its literals, see below. claims: nothing extra; AddTokenAuthentication already registered the claims provider.
--audit none, sqlite none none: NullAuditLog as a Singleton. sqlite: SqliteAuditLog as Scoped — it takes four dependencies and two of them are Scoped under jwt, so a Singleton would capture them.
--platform-version any version 0.3.0 The version every emitted FTS.Platform.* PackageReference is pinned to. No emitted file carries a literal version number.

--db none --auth none therefore produces the none host. As emitted that is four FTS.Platform.* packages and five resolved libraries — Abstractions, .Hosting, .Defaults and .Application, the fourth taken for the layered example in src/<Name>/Example/ and for nothing else. After the deletion the template documents — the Example/ directory, its test file, that test project's ProjectReference and the FTS.Platform.Application PackageReference — it is exactly FTS.Platform.Abstractions, .Hosting and .Defaults, four resolved libraries. Both are no third-party package at all. That is ADR 0007's "Abstractions + Hosting + Defaults is the whole cost of wanting none of the seams", scaffolded rather than described.

Authorization is deliberately not a parameter and not registered. RequirePermission without AddPermissionAuthorization fails at request time, so no emitted endpoint names a permission. A jwt scaffold still refuses an unauthenticated request, because the fallback policy AddTokenAuthentication installs needs no authorization provider. Adding the authorization seam is a deliberate later step, not a flag.

Two combinations are refused, at scaffold time

Both exit 102, name the option, and write no file — rather than generating a project that fails to compile or, worse, one that starts and cannot resolve a dependency.

Refused Why
--tenancy claims without --auth jwt Claims tenancy reads the tenant from a validated token claim. Without jwt there are no claims to read.
--audit sqlite without --db sqlite SqliteAuditLog takes an IDbConnectionFactory and an IClock among its four constructor parameters, and both arrive only with the SQLite provider.

dotnet new fts-api --help prints each refusal as a parameter with an Enabled if: condition, so the rule is documented by the same lines that enforce it.

What a --auth jwt scaffold needs before it starts

AuthOptions.Resolve runs as the host's first statement and refuses, naming the missing key, rather than starting a host that would reject every token at request time. The emitted appsettings.json therefore carries the key NAMES and, for three of them, no value:

Key Emitted as Who supplies it
Auth:Mode "jwt" Pinned by the template. An absent Mode resolves to header mode in Development, and this host registered only the jwt providers.
Auth:Audience "" You. Every token is checked against it.
Auth:Authority "" You — the OIDC authority whose JWKS supplies signing keys.
Auth:TenantKind "" You. The platform ships no tenant-kind constants: that vocabulary belongs to your project, which is exactly why the template will not choose one.

For offline use, delete Auth:Authority and supply Auth:Issuer and Auth:SigningKey instead — a symmetric key of at least 32 UTF-8 bytes. Configure exactly one of Authority and SigningKey; both is ambiguous and AuthOptions.Resolve says so.

No Auth:SigningKey is emitted, blank or otherwise. This package is public, and a key-shaped literal in a public template is the one thing here that could become a real credential. Any of these keys can also arrive from the environment as Auth__Audience, Auth__Issuer, Auth__SigningKey, Auth__TenantKind, which override the file.

What a --tenancy fixed scaffold must replace

builder.Services.AddSingleton<ITenantContext>(new SingleTenantContext(new TenantRef("tenant", "default")));

Both literals are placeholders. "tenant" is the kind and "default" is the id, and they are the generic words for the slots rather than words about anything. The fixed provider takes a constant, so some pair has to be emitted; choosing a meaningful one would be this package deciding your vocabulary for you, which is the one thing the platform refuses to do anywhere else.


fts-module — a module project

An item template. dotnet new fts-module -n Billing -o src/Modules/Billing writes ten files into that directory, in the layered shape ADR 0011 records:

Billing.csproj                            Abstractions + Application + Hosting, and the Migrations glob
BillingModule.cs                          the IModule implementation - the composition root
Domain/Widget.cs                          the entity that carries the rule
Application/ArchiveWidget.cs              the use case: ArchiveWidgetCommand + IWriteHandler
Application/IWidgetStore.cs               the persistence port
Infrastructure/InMemoryWidgetStore.cs     the mechanism, and the one file you replace first
Api/BillingEndpoints.cs                   two routes under /billing, both AllowAnonymous
Migrations/001_initial.sql                one script, so the glob is matching something real
Tests/Billing.Tests.csproj                xunit.v3, and a reference to the module beside it
Tests/WidgetTests.cs                      the emitted rule, in memory: no host and no database

The store is a dictionary and not a database by measurement, not by timidity: IDbConnectionFactory and IClock are registered only by --db sqlite, so a store injecting either would throw at builder.Build() in Development — where ValidateOnBuild is on — in exactly the composition a developer scaffolds first. The same module therefore compiles and runs under every combination of the four seam flags.

Parameter Default Effect
--module-id the lowercased -n name The IModule.Id. -n Billing alone yields billing.
--platform-version 0.3.0 As above.

IModule.Id is contracted stable and lowercase: it is the key AddModules registers the module under, and it lands in the module_id column of every audit row the module writes. The default is generated from the name rather than typed, so it is never the template's own word — but it is lowercased without hyphenating, so -n InventoryTracking yields inventorytracking. A multi-word id is what --module-id is for:

dotnet new fts-module -n InventoryTracking -o src/Modules/InventoryTracking --module-id inventory-tracking

Because that default is generated rather than literal, dotnet new fts-module --help prints Type: string for --module-id with no Default: line. The emitted Id is where you can see it.

It modifies no existing file

The template writes into its output directory and nowhere else. Registering the module in a host is four edits, and they are emitted as the header comment of the generated *Module.cs with your own names already substituted:

1.  dotnet sln add src/Modules/Billing/Billing.csproj
2.  dotnet sln add src/Modules/Billing/Tests/Billing.Tests.csproj
3.  dotnet add src/<host>/<host>.csproj reference src/Modules/Billing/Billing.csproj
4.  in the host's Program.cs, with no `using` at all:
    builder.Services.AddModules(builder.Configuration, new Billing.BillingModule());

The argument is namespace-qualified and there is no using, so the documented manual edit and the one fts add module makes are the same edit.

fts add module does that for you. It is still not this package's job: an item template that reached out of its output directory to edit files it did not create is a template you could not run twice, and could not read the diff of. The split is the reason the tool exists at all.

The example module fts-api emits is a directory of files in the host project, not a project — which is what keeps the none scaffold at five resolved libraries as emitted and four after the deletion. fts-module emits a project because that is the shape a module that is not an example has.


Restoring what a scaffold references

The emitted nuget.config declares two sources and no credential: the FTS feed, and nuget.org — which is required rather than optional, because FTS.Platform.Sqlite carries Microsoft.Data.Sqlite, dbup-sqlite and Dapper, FTS.Platform.Auth carries Microsoft.AspNetCore.Authentication.JwtBearer, and the test project takes xunit.v3, xunit.runner.visualstudio and Microsoft.NET.Test.Sdk. None of those is on the FTS feed. The none composition takes nothing from nuget.org at all.

GitHub Packages has no anonymous read for NuGet: even a public package needs a token. A 401 on restore means no token reached NuGet; a 403 on the download after a 200 on the index means the token lacks the read:packages scope. Keep the credential out of source control — supply it from the environment, or from a nuget.config that is not committed.

Uninstalling

dotnet new uninstall FTS.Platform.Templates
  • .NETStandard 2.0

    • No dependencies.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.0 0 9/12/2026
0.5.0 0 9/9/2026
0.3.0 0 9/9/2026
0.2.0 0 9/8/2026
0.1.0 0 9/8/2026