Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ indent_size = 2
indent_size = 4
tab_width = 4

# New line preferences
end_of_line = crlf
insert_final_newline = false
# New line preferences
end_of_line = lf
insert_final_newline = true

#### .NET Coding Conventions ####
[*.{cs,vb}]
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Build

on:
pull_request:
push:
branches:
- main

jobs:
build:
name: Build and test
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Restore
run: dotnet restore CleanApiStarter.slnx

- name: Format
run: dotnet format CleanApiStarter.slnx --verify-no-changes --no-restore

- name: Build
run: dotnet build CleanApiStarter.slnx --no-restore --configuration Release /nr:false -v:minimal

- name: Test
run: dotnet test CleanApiStarter.slnx --no-build --configuration Release /nr:false -v:minimal
44 changes: 44 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: CodeQL

on:
pull_request:
push:
branches:
- main
schedule:
- cron: '00 0 * * 1'

permissions:
actions: read
contents: read
security-events: write

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: csharp
build-mode: manual
queries: security-extended,security-and-quality

- name: Restore
run: dotnet restore CleanApiStarter.slnx

- name: Build
run: dotnet build CleanApiStarter.slnx --no-restore --configuration Release /nr:false -v:minimal

- name: Analyze
uses: github/codeql-action/analyze@v4
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release

on:
release:
types: [published]

permissions:
contents: read

jobs:
publish:
name: Publish to NuGet.org
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json

- name: Extract version
id: version
run: echo "value=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT

- name: Update template version
run: |
jq --arg v "${{ steps.version.outputs.value }}" \
'.symbols.caPackageVersion.defaultValue = $v' \
.template.config/template.json > tmp.json && mv tmp.json .template.config/template.json

- name: Pack template
env:
RELEASE_NOTES: ${{ github.event.release.body }}
run: dotnet pack CleanApiStarter.Template.csproj --configuration Release --output artifacts -p:PackageVersion=${{ steps.version.outputs.value }} -p:PackageReleaseNotes="$RELEASE_NOTES"

- name: Upload package artifact
uses: actions/upload-artifact@v7
with:
name: nuget-package
path: artifacts/*.nupkg

- name: Publish to NuGet
run: dotnet nuget push artifacts/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate
46 changes: 46 additions & 0 deletions .github/workflows/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Template

on:
pull_request:
push:
branches:
- main

jobs:
template:
name: Verify template output
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: global.json

- name: Pack template
run: dotnet pack CleanApiStarter.Template.csproj --configuration Release --output artifacts

- name: Install template
run: dotnet new install artifacts/CleanApiStarter.Template.0.0.0.nupkg --force

- name: Create sample
run: dotnet new clean-api-starter -n DemoProduct -o artifacts/DemoProduct

- name: Restore sample
working-directory: artifacts/DemoProduct
run: dotnet restore DemoProduct.slnx

- name: Format sample
working-directory: artifacts/DemoProduct
run: dotnet format DemoProduct.slnx --verify-no-changes --no-restore

- name: Build sample
working-directory: artifacts/DemoProduct
run: dotnet build DemoProduct.slnx --no-restore --configuration Release /nr:false -v:minimal

- name: Test sample
working-directory: artifacts/DemoProduct
run: dotnet test DemoProduct.slnx --no-build --configuration Release /nr:false -v:minimal
51 changes: 51 additions & 0 deletions .template.config/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Chathuranga",
"classifications": [
"Web",
"API",
"Clean Architecture",
"Aspire",
"PostgreSQL",
"OpenTelemetry"
],
"identity": "CleanApiStarter.Template",
"name": "Clean API Starter",
"shortName": "clean-api-starter",
"sourceName": "CleanApiStarter",
"preferNameDirectory": true,
"symbols": {
"caPackageVersion": {
"type": "parameter",
"datatype": "text",
"defaultValue": "0.0.0",
"replaces": "0.0.0"
}
},
"tags": {
"language": "C#",
"type": "solution"
},
"sources": [
{
"modifiers": [
{
"exclude": [
".git/**",
".idea/**",
".junie/**",
".vs/**",
"**/.DS_Store",
"**/bin/**",
"**/obj/**",
"artifacts/**",
".github/workflows/release.yml",
".github/workflows/template.yml",
"CleanApiStarter.Template.csproj",
"scripts/install-template.sh"
]
}
]
}
]
}
13 changes: 12 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ This repository is a Clean Architecture API starter template named `CleanApiStar
- Keep `PackageVersion` items sorted alphabetically by `Include`.
- Do not add package versions directly in individual `.csproj` files.

## Template Packaging

- This repository is also the `dotnet new` template source.
- Keep template metadata in `.template.config/template.json`.
- Keep NuGet template package metadata in `CleanApiStarter.Template.csproj`.
- Use `dotnet pack` and `dotnet nuget push` for template packaging and publishing. Do not use `nuget pack`, `nuget.exe`, or Mono.
- Use `scripts/install-template.sh` to pack and install the local template.
- Keep repo-only template packaging scripts excluded from generated template output.
- Keep CodeQL security scanning in `.github/workflows/codeql.yml`, and allow generated projects to inherit it.
- Keep release publishing triggered by GitHub Release publication with `vX.Y.Z` tags, not manual version inputs.

## Testing

- Use xUnit v3, AutoFixture.xUnit3, AutoFixture.AutoNSubstitute, NSubstitute, and Shouldly for unit tests.
Expand All @@ -163,7 +174,7 @@ This repository is a Clean Architecture API starter template named `CleanApiStar
- After structural or package changes, run:

```bash
dotnet restore CleanApiStarter.slnx --disable-parallel
dotnet restore CleanApiStarter.slnx
dotnet build CleanApiStarter.slnx --no-restore /nr:false -v:minimal
```

Expand Down
31 changes: 31 additions & 0 deletions CleanApiStarter.Template.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<EnableDefaultItems>false</EnableDefaultItems>
<IsPackable>true</IsPackable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<NoDefaultExcludes>true</NoDefaultExcludes>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<NoWarn>$(NoWarn);NU5128</NoWarn>

<PackageId>CleanApiStarter.Template</PackageId>
<Version>0.0.0</Version>
<Title>Clean API Starter Template</Title>
<Authors>Chathuranga</Authors>
<Description>Clean Architecture API starter template with .NET, Aspire, PostgreSQL, OpenTelemetry, Scalar, JWT authentication, EF Core, and tests.</Description>
<PackageTags>dotnet-new;template;aspnetcore;api;clean-architecture;aspire;postgresql;opentelemetry;scalar;jwt;efcore</PackageTags>
<PackageType>Template</PackageType>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="LICENSE" Pack="true" PackagePath="" />
<None Include="README.md" Pack="true" PackagePath="" />
<Content
Include="**/*"
Exclude=".git/**/*;.idea/**/*;.junie/**/*;.vs/**/*;**/bin/**/*;**/obj/**/*;artifacts/**/*;**/.DS_Store;CleanApiStarter.Template.csproj;scripts/install-template.sh;.github/workflows/release.yml;.github/workflows/template.yml;*.nupkg"
Pack="true"
PackagePath="content/%(Identity)" />
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions CleanApiStarter.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
<Folder Name="/Solution Items/">
<File Path=".config/dotnet-tools.json" />
<File Path=".editorconfig" />
<File Path=".github/workflows/build.yml" />
<File Path=".github/workflows/codeql.yml" />
<File Path=".github/workflows/release.yml" />
<File Path=".github/workflows/template.yml" />
<File Path=".gitignore" />
<File Path=".template.config/template.json" />
<File Path="CleanApiStarter.Template.csproj" />
<File Path="docker-compose.yml" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="LICENSE" />
<File Path="README.md" />
</Folder>
<Folder Name="/database/" />
Expand All @@ -25,6 +32,10 @@
<Project Path="src/CleanApiStarter.AspNetCore/CleanApiStarter.AspNetCore.csproj" />
<Project Path="src/CleanApiStarter.Configuration/CleanApiStarter.Configuration.csproj" />
</Folder>
<Folder Name="/scripts/">
<File Path="scripts/install-template.sh" />
<File Path="scripts/test-coverage.sh" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/CleanApiStarter.Tests/CleanApiStarter.Tests.csproj" />
<Project Path="tests\CleanApiStarter.Api.IntegrationTests\CleanApiStarter.Api.IntegrationTests.csproj" />
Expand Down
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The project is intentionally both a template and a working reference application
- xUnit v3 unit tests with AutoFixture, AutoFixture.AutoNSubstitute, NSubstitute, and Shouldly.
- MSTest API integration tests with Testcontainers for PostgreSQL.
- Local coverage script that generates an HTML report with ReportGenerator.
- GitHub Actions CI with build, test, template verification, and CodeQL security scanning.

## Solution Layout

Expand Down Expand Up @@ -76,6 +77,41 @@ The SDK is pinned in `global.json`:
}
```

## Use As A Template

Install the template package from NuGet:

```bash
dotnet new install CleanApiStarter.Template
```

Create a new solution:

```bash
dotnet new clean-api-starter -n MyProduct
```

The template replaces `CleanApiStarter` in solution, project, file, and namespace names. For example, `CleanApiStarter.Api` becomes `MyProduct.Api`.

While developing the template locally, run:

```bash
scripts/install-template.sh
```

That script:

- uninstalls the previous local template package
- packs the current repo with `CleanApiStarter.Template.csproj`
- installs the generated local `.nupkg`
- deletes the temporary package from `artifacts`

Then create a local test solution:

```bash
dotnet new clean-api-starter -n DemoProduct
```

## Run With Aspire

```bash
Expand Down Expand Up @@ -474,10 +510,19 @@ artifacts/coverage/report/index.html
Restore and build:

```bash
dotnet restore CleanApiStarter.slnx --disable-parallel
dotnet restore CleanApiStarter.slnx
dotnet build CleanApiStarter.slnx --no-restore /nr:false -v:minimal
```

## CI

GitHub Actions workflows live in `.github/workflows`:

- `build.yml` restores, builds, and tests the repository on pull requests and pushes to `main`.
- `codeql.yml` runs CodeQL static security analysis on pull requests, pushes to `main`, and weekly on Monday.
- `template.yml` packs the template, installs it locally, creates a sample solution, then restores, builds, and tests the generated output.
- `release.yml` publishes the template to NuGet when a GitHub Release is published with a tag such as `v1.0.0`.

## Package Management

Package versions are managed centrally in:
Expand Down
Loading
Loading