How to use
π Linux / macOS
Run the following command in your terminal. Replace MyProjectName with your desired solution name.
# Syntax: curl [url] | bash -s -- [ProjectName]
curl -fsSL https://opengist.rmrf.online/weehong/7548f80484b4419b9ba93ca45e784638/raw/HEAD/scaffold.sh | bash -s -- MyCleanApp
bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/7548f80484b4419b9ba93ca45e784638/raw/HEAD/scaffold_project.sh)"
πͺ Windows (PowerShell)
Run this command to scaffold the solution.
# Syntax: irm [url] | % { & ([scriptblock]::Create($_)) [ProjectName] }
irm "https://opengist.rmrf.online/weehong/7548f80484b4419b9ba93ca45e784638/raw/HEAD/scaffold.ps1" | % { & ([scriptblock]::Create($_)) Tidverse }
10. Test Projects
Test Strategy
The boilerplate scaffolds three test projects, each targeting a different layer and testing style:
| Project | Tests | Style |
|---|---|---|
${PROJECT}.Domain.Tests |
Entities, value objects, Result pattern, domain logic | Pure unit tests β no mocks needed (Domain has zero dependencies) |
${PROJECT}.Application.Tests |
Command/query handlers, validators, pipeline behaviors | Unit tests with mocked ApplicationDbContext and IUnitOfWork |
${PROJECT}.IntegrationTests |
Full HTTP request/response cycle through the API | Integration tests using WebApplicationFactory<Program> with a real PostgreSQL instance |
Shared test tooling across all projects:
- xUnit β test framework (
[Fact],[Theory]) - FluentAssertions β expressive assertions (
result.Should().Be(...)) - Moq β mocking framework for isolating dependencies
- coverlet.collector β code coverage collection (used by CI pipeline)
Integration test additions:
Microsoft.AspNetCore.Mvc.Testingβ providesWebApplicationFactory<Program>for in-process HTTP testing
Test File Naming Convention
Test files follow the pattern {ClassUnderTest}Tests.cs. For example:
ResultTests.cstestsResult.csValidationBehaviorTests.cstestsValidationBehavior.cs
Project References
Each test project references only the layer it tests:
Domain.TestsβDomainApplication.TestsβApplication(which transitively includes Domain)IntegrationTestsβApi(which transitively includes everything)
This mirrors the Clean Architecture dependency rule β test projects never reach across layers.
11. CI/CD Pipeline
The CI/CD pipeline is split into two conceptual sections:
- Standard CI (test, SonarCloud, Qodana) β reusable as-is across projects. Just update repository variables.
- Deployment (Docker build/push, SSH deploy) β project-specific. Customize the deployment target, Tailscale config, and SSH details.
qodana.yaml
Configuration for JetBrains Qodana static analysis. Points to the .slnx solution file and uses the starter inspection profile.
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# [https://www.jetbrains.com/help/qodana/qodana-yaml.html](https://www.jetbrains.com/help/qodana/qodana-yaml.html) #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify IDE code to run analysis without container (Applied in CI/CD pipeline)
ide: QDNET
#Specify the .NET solution to analyze
dotnet:
solution: ${PROJECT}.slnx
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at [https://plugins.jetbrains.com](https://plugins.jetbrains.com))
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
.github/workflows/ci.yml
Full CI/CD pipeline with four jobs:
name: CI/CD Pipeline
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
env:
DOTNET_VERSION: "10.0.x"
JAVA_VERSION: "17"
DOCKER_IMAGE: ${{ vars.DOCKERHUB_USERNAME }}/${PROJECT_LOWER}-api
jobs:
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Job 1: Build, test, and collect coverage
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
test:
name: Build & Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_DB: ${PROJECT_LOWER}_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore dependencies
run: dotnet restore
- name: Build solution
run: dotnet build --no-restore --configuration Release
- name: Run tests
run: >-
dotnet test
--no-build
--configuration Release
--logger "trx;LogFileName=test-results.trx"
--collect:"XPlat Code Coverage"
--results-directory ./TestResults
env:
ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=${PROJECT_LOWER}_test;Username=postgres;Password=postgres"
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: ./TestResults
retention-days: 7
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Job 2: SonarCloud analysis
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
sonar:
name: SonarCloud Analysis
runs-on: ubuntu-latest
needs: test
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_DB: ${PROJECT_LOWER}_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- name: Install SonarScanner
run: dotnet tool install --global dotnet-sonarscanner
- name: Begin SonarCloud analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: >-
dotnet sonarscanner begin
/k:"${{ vars.SONAR_PROJECT_KEY }}"
/o:"${{ vars.SONAR_ORGANIZATION_KEY }}"
/d:sonar.token="${{ secrets.SONAR_TOKEN }}"
/d:sonar.host.url="[https://sonarcloud.io](https://sonarcloud.io)"
/d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml"
/d:sonar.exclusions="**/obj/**,**/bin/**"
/d:sonar.coverage.exclusions="**/obj/**,**/bin/**,**/Migrations/**"
- name: Build solution
run: dotnet build --configuration Release
- name: Run tests with coverage
run: >-
dotnet test
--no-build
--configuration Release
--collect:"XPlat Code Coverage;Format=opencover"
--results-directory ./TestResults
env:
ConnectionStrings__DefaultConnection: "Host=localhost;Port=5432;Database=${PROJECT_LOWER}_test;Username=postgres;Password=postgres"
- name: End SonarCloud analysis
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
- name: Check SonarCloud Quality Gate
uses: sonarsource/sonarqube-quality-gate-action@v1.2.0
timeout-minutes: 5
continue-on-error: true
with:
scanMetadataReportFile: .sonarqube/out/.sonar/report-task.txt
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Job 3: Qodana analysis (parallel with SonarCloud)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
qodana:
name: Qodana Analysis
runs-on: ubuntu-latest
needs: test
permissions:
contents: write
pull-requests: write
checks: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Qodana Scan
uses: JetBrains/qodana-action@v2025.1
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Job 4: Build, push, and deploy
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
deploy:
name: Deploy to Server
runs-on: ubuntu-latest
needs: [sonar, qodana]
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
environment: Development
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.DOCKER_IMAGE }}:latest
${{ env.DOCKER_IMAGE }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Connect to Tailscale
uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ vars.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
- name: Copy compose file to server
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ vars.SSH_HOST }}
username: ${{ vars.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ vars.SSH_PORT }}
source: "compose.yml,.env.example"
target: ${{ vars.DEPLOY_PATH }}
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ vars.SSH_HOST }}
username: ${{ vars.SSH_USERNAME }}
key: ${{ secrets.SSH_KEY }}
port: ${{ vars.SSH_PORT }}
envs: DOCKER_IMAGE,IMAGE_TAG
script: |
cd ${{ vars.DEPLOY_PATH }}
if [ ! -f .env ]; then
cp .env.example .env
chmod 600 .env
fi
# Update DOCKER_IMAGE and IMAGE_TAG in .env with values from CI
sed -i "s|^DOCKER_IMAGE=.*|DOCKER_IMAGE=${DOCKER_IMAGE}|" .env
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${IMAGE_TAG}|" .env
docker compose pull
docker compose up -d
sleep 10
# Verify all expected services are running
EXPECTED=2
RUNNING=$(docker compose ps --status running --quiet | wc -l)
if [ "$RUNNING" -lt "$EXPECTED" ]; then
echo "Expected $EXPECTED services, found $RUNNING running"
docker compose logs --tail=50
exit 1
fi
env:
DOCKER_IMAGE: ${{ env.DOCKER_IMAGE }}
IMAGE_TAG: ${{ github.sha }}
Required GitHub Secrets and Variables
| Type | Name | Description |
|---|---|---|
| Secret | SONAR_TOKEN |
SonarCloud authentication token |
| Secret | QODANA_TOKEN |
Qodana Cloud authentication token |
| Secret | DOCKERHUB_TOKEN |
Docker Hub access token |
| Secret | SSH_KEY |
Private SSH key for deployment server |
| Secret | TS_OAUTH_SECRET |
Tailscale OAuth secret |
| Variable | DOCKERHUB_USERNAME |
Docker Hub username |
| Variable | SONAR_PROJECT_KEY |
SonarCloud project key |
| Variable | SONAR_ORGANIZATION_KEY |
SonarCloud organization key |
| Variable | SSH_HOST |
Deployment server hostname (Tailscale IP) |
| Variable | SSH_USERNAME |
SSH username on deployment server |
| Variable | SSH_PORT |
SSH port on deployment server |
| Variable | TS_OAUTH_CLIENT_ID |
Tailscale OAuth client ID |
| Variable | DEPLOY_PATH |
Path on server where app is deployed |
12. AI-Assisted Development
.github/copilot-instructions.md
This file provides project-specific context to GitHub Copilot (and other AI assistants that read it). It documents:
- Git commit conventions β execute commits directly, no
Co-authored-bytrailers - Build & run commands β
dotnet build,dotnet run,dotnet test, EF Core migrations - Architecture β Clean Architecture dependency flow, layer responsibilities
- Key conventions β CQRS with MediatR, Result pattern, Domain layer rules
- Testing conventions β xUnit, Moq, FluentAssertions,
WebApplicationFactory<Program> - Tech stack β .NET 10, PostgreSQL, MediatR 14, FluentValidation 12, Serilog
This file is automatically picked up by Copilot in VS Code and GitHub.com, providing contextual suggestions that align with the project's architecture and conventions.
13. Running the Project
Local Development (without Docker)
# Start PostgreSQL (via Docker or locally)
docker compose up db -d
# Run the API
dotnet run --project src/${PROJECT}.Api
# API available at http://localhost:5212
# Health check: http://localhost:5212/health
# OpenAPI spec: http://localhost:5212/openapi/v1.json (dev only)
Docker Compose (full stack)
# Build and start everything
docker compose up --build -d
# API available at http://localhost:5212
# Health check: http://localhost:5212/health
Running Tests
# All tests
dotnet test
# Specific test project
dotnet test tests/${PROJECT}.Domain.Tests
dotnet test tests/${PROJECT}.Application.Tests
dotnet test tests/${PROJECT}.IntegrationTests
# Single test by name
dotnet test --filter "FullyQualifiedName~MyTestMethod"
EF Core Migrations
# Add a new migration
dotnet ef migrations add <MigrationName> \
--project src/${PROJECT}.Infrastructure \
--startup-project src/${PROJECT}.Api
# Apply migrations
dotnet ef database update \
--project src/${PROJECT}.Infrastructure \
--startup-project src/${PROJECT}.Api
Appendix A: Outbox Pattern (Optional)
This section is an optional enhancement. The boilerplate uses EF Core's
DomainEventInterceptorto dispatch domain events in-process duringSaveChanges. The outbox pattern is the next evolution β use it when you need guaranteed delivery of domain events to external systems (message brokers, other microservices) with at-least-once semantics.
The Problem
The current DomainEventInterceptor dispatches events via MediatR inside the SaveChanges pipeline. This works well for in-process handlers (updating read models, sending notifications, etc.), but has two limitations:
- No guarantee of delivery to external systems β if the application crashes after
SaveChangesbut before an external message is sent, the event is lost. - Coupling to the transaction boundary β if an event handler calls an external API or publishes to a message broker, you're mixing I/O with the database transaction.
The Outbox Pattern Solution
Instead of dispatching events immediately, write them as rows in an OutboxMessages table within the same database transaction as the business data. A background job (Quartz.NET, which is already included in this boilerplate) polls the table and publishes events to external consumers.
Flow:
- Handler modifies entity + raises domain event
SaveChangesinterceptor serializes domain events intoOutboxMessagestable (same transaction)- Transaction commits β business data and outbox messages are atomically consistent
- Quartz.NET background job polls
OutboxMessages, publishes to message broker, marks as processed
Implementation Sketch
1. Outbox entity (Domain or Infrastructure layer):
public sealed class OutboxMessage
{
public Guid Id { get; init; }
public string Type { get; init; } = string.Empty; // Event CLR type name
public string Content { get; init; } = string.Empty; // Serialized event payload (JSON)
public DateTime OccurredOnUtc { get; init; }
public DateTime? ProcessedOnUtc { get; set; }
public string? Error { get; set; }
}
2. Modified interceptor (writes to outbox instead of dispatching):
// In SaveChangesInterceptor, replace MediatR Publish with:
var outboxMessages = domainEvents.Select(e => new OutboxMessage
{
Id = Guid.NewGuid(),
Type = e.GetType().Name,
Content = JsonConvert.SerializeObject(e, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
}),
OccurredOnUtc = DateTime.UtcNow
});
dbContext.Set<OutboxMessage>().AddRange(outboxMessages);
// Events are persisted in the same SaveChanges transaction
3. Quartz.NET background job:
[DisallowConcurrentExecution]
public sealed class ProcessOutboxMessagesJob(
ApplicationDbContext dbContext,
IPublisher publisher) : IJob
{
public async Task Execute(IJobExecutionContext context)
{
var messages = await dbContext.Set<OutboxMessage>()
.Where(m => m.ProcessedOnUtc == null)
.OrderBy(m => m.OccurredOnUtc)
.Take(20)
.ToListAsync(context.CancellationToken);
foreach (var message in messages)
{
try
{
var domainEvent = JsonConvert.DeserializeObject<IDomainEvent>(
message.Content,
new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
if (domainEvent is not null)
await publisher.Publish(domainEvent, context.CancellationToken);
message.ProcessedOnUtc = DateTime.UtcNow;
}
catch (Exception ex)
{
message.Error = ex.ToString();
}
}
await dbContext.SaveChangesAsync(context.CancellationToken);
}
}
When to Adopt This
- You're publishing events to a message broker (RabbitMQ, Azure Service Bus, Kafka)
- You need at-least-once delivery guarantees
- You're in a microservices architecture where services communicate via events
For purely in-process event handling (the common case in a monolith), the existing DomainEventInterceptor approach is simpler and sufficient.
| 1 | param( |
| 2 | [string]$ProjectName, |
| 3 | [ValidateSet("slnx", "sln")] |
| 4 | [string]$Format = "slnx", |
| 5 | [switch]$SkipPackages = $false |
| 6 | ) |
| 7 | |
| 8 | # --- 1. Setup & Validation --- |
| 9 | if ([string]::IsNullOrWhiteSpace($ProjectName)) { |
| 10 | $ProjectName = Read-Host "Please enter a project name" |
| 11 | } |
| 12 | |
| 13 | if ([string]::IsNullOrWhiteSpace($ProjectName)) { |
| 14 | Write-Host "β Name required." -ForegroundColor Red; exit 1 |
| 15 | } |
| 16 | |
| 17 | if (Test-Path $ProjectName) { |
| 18 | Write-Host "β Directory '$ProjectName' already exists. Aborting to prevent overwrite." -ForegroundColor Red; exit 1 |
| 19 | } |
| 20 | |
| 21 | $SlnFile = "$ProjectName.$Format" |
| 22 | Write-Host "π Scaffolding Clean Architecture (DDD) for: $ProjectName" -ForegroundColor Cyan |
| 23 | |
| 24 | # Create Root Directory |
| 25 | New-Item -ItemType Directory -Path $ProjectName | Out-Null |
| 26 | Set-Location $ProjectName |
| 27 | |
| 28 | # --- 2. Smart SDK Detection --- |
| 29 | $LatestSdk = dotnet --list-sdks | Select-Object -Last 1 | ForEach-Object { $_.Split(' ')[0] } |
| 30 | |
| 31 | if ($LatestSdk) { |
| 32 | Write-Host "βΉοΈ Detected SDK: $LatestSdk. Pinning global.json..." -ForegroundColor Gray |
| 33 | dotnet new globaljson --sdk-version $LatestSdk --roll-forward latestFeature |
| 34 | } |
| 35 | |
| 36 | dotnet new gitignore |
| 37 | |
| 38 | # --- 3. Create Solution & Fix NuGet --- |
| 39 | if ($Format -eq "slnx") { dotnet new sln -n $ProjectName --format slnx } |
| 40 | else { dotnet new sln -n $ProjectName } |
| 41 | |
| 42 | Write-Host "π¦ Configuring NuGet sources..." -ForegroundColor Cyan |
| 43 | dotnet new nugetconfig --force |
| 44 | $CurrentSources = dotnet nuget list source --configfile "nuget.config" |
| 45 | if ($CurrentSources -notmatch "nuget.org") { |
| 46 | dotnet nuget add source "https://api.nuget.org/v3/index.json" -n "nuget.org" --configfile "nuget.config" |
| 47 | } |
| 48 | |
| 49 | # --- 4. Create Projects --- |
| 50 | Write-Host "π¨ Creating projects..." -ForegroundColor Cyan |
| 51 | |
| 52 | # Source Projects |
| 53 | dotnet new classlib -n "$ProjectName.Domain" -o "src/$ProjectName.Domain" |
| 54 | dotnet new classlib -n "$ProjectName.Application" -o "src/$ProjectName.Application" |
| 55 | dotnet new classlib -n "$ProjectName.Infrastructure" -o "src/$ProjectName.Infrastructure" |
| 56 | dotnet new webapi -n "$ProjectName.Api" -o "src/$ProjectName.Api" --use-controllers |
| 57 | |
| 58 | # Test Projects |
| 59 | dotnet new xunit -n "$ProjectName.Domain.Tests" -o "tests/$ProjectName.Domain.Tests" |
| 60 | dotnet new xunit -n "$ProjectName.Application.Tests" -o "tests/$ProjectName.Application.Tests" |
| 61 | dotnet new xunit -n "$ProjectName.IntegrationTests" -o "tests/$ProjectName.IntegrationTests" |
| 62 | |
| 63 | # --- 4.1 CLEANUP BOILERPLATE --- |
| 64 | Write-Host "π§Ή Removing default template files..." -ForegroundColor Cyan |
| 65 | |
| 66 | $FilesToRemove = @( |
| 67 | "src/$ProjectName.Domain/Class1.cs", |
| 68 | "src/$ProjectName.Application/Class1.cs", |
| 69 | "src/$ProjectName.Infrastructure/Class1.cs", |
| 70 | "tests/$ProjectName.Domain.Tests/UnitTest1.cs", |
| 71 | "tests/$ProjectName.Application.Tests/UnitTest1.cs", |
| 72 | "tests/$ProjectName.IntegrationTests/UnitTest1.cs", |
| 73 | "src/$ProjectName.Api/WeatherForecast.cs", |
| 74 | "src/$ProjectName.Api/Controllers/WeatherForecastController.cs", |
| 75 | "src/$ProjectName.Api/$ProjectName.Api.http" |
| 76 | ) |
| 77 | |
| 78 | foreach ($File in $FilesToRemove) { |
| 79 | if (Test-Path $File) { |
| 80 | Remove-Item $File -Force |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | # --- 5. Add to Solution --- |
| 85 | Write-Host "π Organizing solution structure..." -ForegroundColor Cyan |
| 86 | |
| 87 | # Add Src |
| 88 | dotnet sln $SlnFile add "src/$ProjectName.Domain/$ProjectName.Domain.csproj" -s "src" |
| 89 | dotnet sln $SlnFile add "src/$ProjectName.Application/$ProjectName.Application.csproj" -s "src" |
| 90 | dotnet sln $SlnFile add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" -s "src" |
| 91 | dotnet sln $SlnFile add "src/$ProjectName.Api/$ProjectName.Api.csproj" -s "src" |
| 92 | |
| 93 | # Add Tests |
| 94 | dotnet sln $SlnFile add "tests/$ProjectName.Domain.Tests/$ProjectName.Domain.Tests.csproj" -s "tests" |
| 95 | dotnet sln $SlnFile add "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj" -s "tests" |
| 96 | dotnet sln $SlnFile add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" -s "tests" |
| 97 | |
| 98 | # --- 6. Add References --- |
| 99 | Write-Host "π Wiring up dependencies..." -ForegroundColor Cyan |
| 100 | |
| 101 | # Application -> Domain |
| 102 | dotnet add "src/$ProjectName.Application/$ProjectName.Application.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj" |
| 103 | |
| 104 | # Infrastructure -> Application & Domain |
| 105 | dotnet add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj" |
| 106 | dotnet add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj" |
| 107 | |
| 108 | # API -> Application & Infrastructure |
| 109 | dotnet add "src/$ProjectName.Api/$ProjectName.Api.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj" |
| 110 | dotnet add "src/$ProjectName.Api/$ProjectName.Api.csproj" reference "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" |
| 111 | |
| 112 | # Tests |
| 113 | dotnet add "tests/$ProjectName.Domain.Tests/$ProjectName.Domain.Tests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj" |
| 114 | |
| 115 | dotnet add "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj" |
| 116 | # NOTE: Application tests usually need Domain too for entities |
| 117 | dotnet add "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj" |
| 118 | |
| 119 | dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Api/$ProjectName.Api.csproj" |
| 120 | dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" |
| 121 | dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj" |
| 122 | dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj" |
| 123 | |
| 124 | # --- 7. Install Nuget Packages (Optional) --- |
| 125 | if (-not $SkipPackages) { |
| 126 | Write-Host "π¦ Installing standard Clean Architecture packages..." -ForegroundColor Cyan |
| 127 | |
| 128 | function Add-Package { |
| 129 | param ($Project, $Package) |
| 130 | Write-Host " + Adding $Package..." -ForegroundColor Gray |
| 131 | dotnet add $Project package $Package |
| 132 | } |
| 133 | |
| 134 | # Application Layer |
| 135 | Add-Package "src/$ProjectName.Application/$ProjectName.Application.csproj" "MediatR" |
| 136 | Add-Package "src/$ProjectName.Application/$ProjectName.Application.csproj" "FluentValidation" |
| 137 | Add-Package "src/$ProjectName.Application/$ProjectName.Application.csproj" "FluentValidation.DependencyInjectionExtensions" |
| 138 | Add-Package "src/$ProjectName.Application/$ProjectName.Application.csproj" "Microsoft.Extensions.Logging.Abstractions" |
| 139 | |
| 140 | # Infrastructure Layer |
| 141 | Add-Package "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" "Microsoft.EntityFrameworkCore" |
| 142 | Add-Package "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.SqlServer" |
| 143 | Add-Package "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Design" |
| 144 | |
| 145 | # API Layer |
| 146 | Add-Package "src/$ProjectName.Api/$ProjectName.Api.csproj" "Microsoft.EntityFrameworkCore.Tools" |
| 147 | |
| 148 | # Test Projects |
| 149 | $TestProjects = @( |
| 150 | "tests/$ProjectName.Domain.Tests/$ProjectName.Domain.Tests.csproj", |
| 151 | "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj", |
| 152 | "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" |
| 153 | ) |
| 154 | foreach ($proj in $TestProjects) { |
| 155 | Add-Package $proj "FluentAssertions" |
| 156 | Add-Package $proj "Moq" |
| 157 | } |
| 158 | |
| 159 | Add-Package "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" "Microsoft.AspNetCore.Mvc.Testing" |
| 160 | } |
| 161 | |
| 162 | # --- 8. Final Verification --- |
| 163 | Write-Host "ποΈ Verifying build..." -ForegroundColor Cyan |
| 164 | dotnet build |
| 165 | if ($LASTEXITCODE -eq 0) { |
| 166 | Write-Host "β $ProjectName scaffolded successfully!" -ForegroundColor Green |
| 167 | Write-Host " π cd $ProjectName" -ForegroundColor Gray |
| 168 | } |
| 1 | #!/bin/bash |
| 2 | |
| 3 | # Usage: ./scaffold.sh MyProjectName [slnx|sln] [true|false for packages] |
| 4 | # Example: ./scaffold.sh MyCleanApp slnx |
| 5 | |
| 6 | # --- 1. Setup & Validation --- |
| 7 | if [ -z "$1" ]; then |
| 8 | echo "β Please provide a project name." |
| 9 | echo "Usage: ./scaffold.sh MyProjectName [slnx|sln]" |
| 10 | exit 1 |
| 11 | fi |
| 12 | |
| 13 | PROJECT_NAME=$1 |
| 14 | FORMAT=${2:-slnx} |
| 15 | INSTALL_PACKAGES=${3:-true} # Default to true |
| 16 | SLN_FILE="$PROJECT_NAME.$FORMAT" |
| 17 | |
| 18 | # Check if directory exists to avoid overwriting |
| 19 | if [ -d "$PROJECT_NAME" ]; then |
| 20 | echo "β Directory '$PROJECT_NAME' already exists. Aborting." |
| 21 | exit 1 |
| 22 | fi |
| 23 | |
| 24 | echo "π Scaffolding $FORMAT solution for: $PROJECT_NAME (Clean Architecture + DDD)" |
| 25 | |
| 26 | # Create Root Directory and enter it |
| 27 | mkdir "$PROJECT_NAME" |
| 28 | cd "$PROJECT_NAME" || exit |
| 29 | |
| 30 | # --- 2. Smart SDK Detection --- |
| 31 | # Get the latest installed SDK version (e.g. 10.0.102) |
| 32 | LATEST_SDK=$(dotnet --list-sdks | tail -n 1 | awk '{print $1}') |
| 33 | |
| 34 | if [ -n "$LATEST_SDK" ]; then |
| 35 | echo "βΉοΈ Detected SDK: $LATEST_SDK. Pinning global.json..." |
| 36 | dotnet new globaljson --sdk-version "$LATEST_SDK" --roll-forward latestFeature |
| 37 | else |
| 38 | echo "β οΈ No SDK detected. Skipping global.json." |
| 39 | fi |
| 40 | |
| 41 | dotnet new gitignore |
| 42 | |
| 43 | # --- 3. Create Solution & Fix NuGet --- |
| 44 | if [ "$FORMAT" == "slnx" ]; then |
| 45 | dotnet new sln -n "$PROJECT_NAME" --format slnx |
| 46 | else |
| 47 | dotnet new sln -n "$PROJECT_NAME" |
| 48 | fi |
| 49 | |
| 50 | # [FIX] Create a local nuget.config to ensure we can find packages |
| 51 | echo "π¦ Configuring NuGet sources..." |
| 52 | dotnet new nugetconfig --force |
| 53 | |
| 54 | # Check if nuget.org is already there |
| 55 | if ! dotnet nuget list source --configfile "nuget.config" | grep -q "nuget.org"; then |
| 56 | dotnet nuget add source "https://api.nuget.org/v3/index.json" -n "nuget.org" --configfile "nuget.config" |
| 57 | fi |
| 58 | |
| 59 | # --- 4. Create Projects --- |
| 60 | echo "π¨ Creating projects..." |
| 61 | dotnet new classlib -n "$PROJECT_NAME.Domain" -o "src/$PROJECT_NAME.Domain" |
| 62 | dotnet new classlib -n "$PROJECT_NAME.Application" -o "src/$PROJECT_NAME.Application" |
| 63 | dotnet new classlib -n "$PROJECT_NAME.Infrastructure" -o "src/$PROJECT_NAME.Infrastructure" |
| 64 | dotnet new webapi -n "$PROJECT_NAME.Api" -o "src/$PROJECT_NAME.Api" --use-controllers |
| 65 | |
| 66 | mkdir -p tests |
| 67 | dotnet new xunit -n "$PROJECT_NAME.Domain.Tests" -o "tests/$PROJECT_NAME.Domain.Tests" |
| 68 | dotnet new xunit -n "$PROJECT_NAME.Application.Tests" -o "tests/$PROJECT_NAME.Application.Tests" |
| 69 | dotnet new xunit -n "$PROJECT_NAME.IntegrationTests" -o "tests/$PROJECT_NAME.IntegrationTests" |
| 70 | |
| 71 | # --- 4.1 CLEANUP BOILERPLATE --- |
| 72 | echo "π§Ή Removing default template files..." |
| 73 | |
| 74 | # Define array of files to remove relative to solution root |
| 75 | FILES_TO_REMOVE=( |
| 76 | "src/$PROJECT_NAME.Domain/Class1.cs" |
| 77 | "src/$PROJECT_NAME.Application/Class1.cs" |
| 78 | "src/$PROJECT_NAME.Infrastructure/Class1.cs" |
| 79 | "tests/$PROJECT_NAME.Domain.Tests/UnitTest1.cs" |
| 80 | "tests/$PROJECT_NAME.Application.Tests/UnitTest1.cs" |
| 81 | "tests/$PROJECT_NAME.IntegrationTests/UnitTest1.cs" |
| 82 | "src/$PROJECT_NAME.Api/WeatherForecast.cs" |
| 83 | "src/$PROJECT_NAME.Api/Controllers/WeatherForecastController.cs" |
| 84 | "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.http" |
| 85 | ) |
| 86 | |
| 87 | for file in "${FILES_TO_REMOVE[@]}"; do |
| 88 | if [ -f "$file" ]; then |
| 89 | rm "$file" |
| 90 | echo " - Deleted $file" |
| 91 | fi |
| 92 | done |
| 93 | |
| 94 | # --- 5. Add to Solution (With Visual Folders) --- |
| 95 | echo "π Organizing solution structure..." |
| 96 | # Source |
| 97 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" -s "src" |
| 98 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" -s "src" |
| 99 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" -s "src" |
| 100 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" -s "src" |
| 101 | |
| 102 | # Tests |
| 103 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" -s "tests" |
| 104 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" -s "tests" |
| 105 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" -s "tests" |
| 106 | |
| 107 | # --- 6. Add References --- |
| 108 | echo "π Wiring up dependencies..." |
| 109 | |
| 110 | # Application -> Domain |
| 111 | dotnet add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 112 | |
| 113 | # Infrastructure -> Application AND Domain |
| 114 | dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 115 | dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 116 | |
| 117 | # API -> Application AND Infrastructure |
| 118 | dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 119 | dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" |
| 120 | |
| 121 | # Tests |
| 122 | dotnet add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 123 | |
| 124 | dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 125 | dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 126 | |
| 127 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" |
| 128 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" |
| 129 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 130 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 131 | |
| 132 | # --- 7. Install Nuget Packages (Optional) --- |
| 133 | if [ "$INSTALL_PACKAGES" = true ]; then |
| 134 | echo "π¦ Installing standard Clean Architecture packages..." |
| 135 | |
| 136 | # Helper function to add packages safely |
| 137 | add_package() { |
| 138 | local proj=$1 |
| 139 | local pkg=$2 |
| 140 | echo " + Adding $pkg..." |
| 141 | dotnet add "$proj" package "$pkg" > /dev/null 2>&1 |
| 142 | if [ $? -ne 0 ]; then |
| 143 | echo " β οΈ Failed to add $pkg. Check connection." |
| 144 | fi |
| 145 | } |
| 146 | |
| 147 | # Application Layer |
| 148 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "MediatR" |
| 149 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation" |
| 150 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation.DependencyInjectionExtensions" |
| 151 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "Microsoft.Extensions.Logging.Abstractions" |
| 152 | |
| 153 | # Infrastructure Layer |
| 154 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore" |
| 155 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.SqlServer" |
| 156 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Design" |
| 157 | |
| 158 | # API Layer |
| 159 | add_package "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" "Microsoft.EntityFrameworkCore.Tools" |
| 160 | |
| 161 | # Tests |
| 162 | TEST_PROJECTS=( |
| 163 | "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" |
| 164 | "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" |
| 165 | "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" |
| 166 | ) |
| 167 | |
| 168 | for proj in "${TEST_PROJECTS[@]}"; do |
| 169 | add_package "$proj" "FluentAssertions" |
| 170 | add_package "$proj" "Moq" |
| 171 | done |
| 172 | |
| 173 | add_package "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" "Microsoft.AspNetCore.Mvc.Testing" |
| 174 | fi |
| 175 | |
| 176 | # --- 8. Final Verification --- |
| 177 | echo "ποΈ Verifying build..." |
| 178 | dotnet build |
| 179 | if [ $? -eq 0 ]; then |
| 180 | echo "β $PROJECT_NAME scaffolded successfully!" |
| 181 | echo "π cd $PROJECT_NAME" |
| 182 | else |
| 183 | echo "β οΈ Scaffolding finished, but build failed. Run 'dotnet restore' manually." |
| 184 | fi |
| 1 | #!/bin/bash |
| 2 | |
| 3 | # --- 0. Determine Project Name --- |
| 4 | # Grab the name of the current directory |
| 5 | PROJECT_NAME=$(basename "$PWD") |
| 6 | |
| 7 | echo "π Using current directory name as project name: $PROJECT_NAME" |
| 8 | |
| 9 | # --- 1. Configuration --- |
| 10 | FORMAT="slnx" # Options: "sln" or "slnx" |
| 11 | INSTALL_PACKAGES=true # Set to true to install NuGet packages |
| 12 | |
| 13 | echo "π Initializing $PROJECT_NAME (Format: $FORMAT)..." |
| 14 | |
| 15 | # --- 1.5 Database Selection --- |
| 16 | echo "" |
| 17 | echo "ποΈ Select your Database Provider for $PROJECT_NAME:" |
| 18 | echo " 1) PostgreSQL (Npgsql) [Default]" |
| 19 | echo " 2) SQL Server" |
| 20 | echo " 3) SQLite" |
| 21 | # < /dev/tty allows interactive prompt when piping via curl |
| 22 | read -p "Enter choice [1-3] (Default: 1): " DB_CHOICE < /dev/tty |
| 23 | |
| 24 | case $DB_CHOICE in |
| 25 | 2) |
| 26 | DB_PACKAGE="Microsoft.EntityFrameworkCore.SqlServer" |
| 27 | echo " Selected: SQL Server" |
| 28 | ;; |
| 29 | 3) |
| 30 | DB_PACKAGE="Microsoft.EntityFrameworkCore.Sqlite" |
| 31 | echo " Selected: SQLite" |
| 32 | ;; |
| 33 | *) |
| 34 | DB_PACKAGE="Npgsql.EntityFrameworkCore.PostgreSQL" |
| 35 | echo " Selected: PostgreSQL" |
| 36 | ;; |
| 37 | esac |
| 38 | echo "" |
| 39 | |
| 40 | # --- 2. Smart SDK Detection --- |
| 41 | # Get the latest installed SDK version |
| 42 | LATEST_SDK=$(dotnet --list-sdks | tail -n 1 | awk '{print $1}') |
| 43 | |
| 44 | if [ -n "$LATEST_SDK" ]; then |
| 45 | echo "βΉοΈ Detected SDK: $LATEST_SDK. Pinning global.json..." |
| 46 | dotnet new globaljson --sdk-version "$LATEST_SDK" --roll-forward latestFeature |
| 47 | else |
| 48 | echo "β οΈ No SDK detected. Skipping global.json." |
| 49 | fi |
| 50 | |
| 51 | dotnet new gitignore |
| 52 | |
| 53 | # --- 3. Create Solution & Fix NuGet --- |
| 54 | if [ "$FORMAT" = "slnx" ]; then |
| 55 | echo "π Creating .slnx solution..." |
| 56 | dotnet new sln -n "$PROJECT_NAME" --format slnx |
| 57 | SLN_FILE="$PROJECT_NAME.slnx" |
| 58 | else |
| 59 | echo "π Creating standard .sln solution..." |
| 60 | dotnet new sln -n "$PROJECT_NAME" |
| 61 | SLN_FILE="$PROJECT_NAME.sln" |
| 62 | fi |
| 63 | |
| 64 | # Create a local nuget.config to ensure we can find packages |
| 65 | echo "π¦ Configuring NuGet sources..." |
| 66 | dotnet new nugetconfig --force |
| 67 | |
| 68 | # Check if nuget.org is already there, if not, add it |
| 69 | if ! dotnet nuget list source --configfile "nuget.config" | grep -q "nuget.org"; then |
| 70 | dotnet nuget add source "https://api.nuget.org/v3/index.json" -n "nuget.org" --configfile "nuget.config" |
| 71 | fi |
| 72 | |
| 73 | # --- 4. Create Projects --- |
| 74 | echo "π¨ Creating projects..." |
| 75 | dotnet new classlib -n "$PROJECT_NAME.Domain" -o "src/$PROJECT_NAME.Domain" |
| 76 | dotnet new classlib -n "$PROJECT_NAME.Application" -o "src/$PROJECT_NAME.Application" |
| 77 | dotnet new classlib -n "$PROJECT_NAME.Infrastructure" -o "src/$PROJECT_NAME.Infrastructure" |
| 78 | dotnet new webapi -n "$PROJECT_NAME.Api" -o "src/$PROJECT_NAME.Api" --use-controllers |
| 79 | |
| 80 | mkdir -p tests |
| 81 | dotnet new xunit -n "$PROJECT_NAME.Domain.Tests" -o "tests/$PROJECT_NAME.Domain.Tests" |
| 82 | dotnet new xunit -n "$PROJECT_NAME.Application.Tests" -o "tests/$PROJECT_NAME.Application.Tests" |
| 83 | dotnet new xunit -n "$PROJECT_NAME.IntegrationTests" -o "tests/$PROJECT_NAME.IntegrationTests" |
| 84 | |
| 85 | # --- 4.1 CLEANUP BOILERPLATE --- |
| 86 | echo "π§Ή Removing default template files..." |
| 87 | |
| 88 | FILES_TO_REMOVE=( |
| 89 | "src/$PROJECT_NAME.Domain/Class1.cs" |
| 90 | "src/$PROJECT_NAME.Application/Class1.cs" |
| 91 | "src/$PROJECT_NAME.Infrastructure/Class1.cs" |
| 92 | "tests/$PROJECT_NAME.Domain.Tests/UnitTest1.cs" |
| 93 | "tests/$PROJECT_NAME.Application.Tests/UnitTest1.cs" |
| 94 | "tests/$PROJECT_NAME.IntegrationTests/UnitTest1.cs" |
| 95 | "src/$PROJECT_NAME.Api/WeatherForecast.cs" |
| 96 | "src/$PROJECT_NAME.Api/Controllers/WeatherForecastController.cs" |
| 97 | "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.http" |
| 98 | ) |
| 99 | |
| 100 | for file in "${FILES_TO_REMOVE[@]}"; do |
| 101 | if [ -f "$file" ]; then |
| 102 | rm "$file" |
| 103 | echo " - Deleted $file" |
| 104 | fi |
| 105 | done |
| 106 | |
| 107 | # --- 5. Add to Solution (With Visual Folders) --- |
| 108 | echo "π Organizing solution structure in $SLN_FILE..." |
| 109 | |
| 110 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" -s "src" |
| 111 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" -s "src" |
| 112 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" -s "src" |
| 113 | dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" -s "src" |
| 114 | |
| 115 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" -s "tests" |
| 116 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" -s "tests" |
| 117 | dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" -s "tests" |
| 118 | |
| 119 | # --- 6. Add References --- |
| 120 | echo "π Wiring up dependencies..." |
| 121 | |
| 122 | # Clean Architecture Flow |
| 123 | dotnet add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 124 | |
| 125 | dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 126 | dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 127 | |
| 128 | dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 129 | dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" |
| 130 | |
| 131 | # Test References |
| 132 | dotnet add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 133 | |
| 134 | dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 135 | dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 136 | |
| 137 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" |
| 138 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" |
| 139 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" |
| 140 | dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" |
| 141 | |
| 142 | # --- 7. Install Nuget Packages (Optional) --- |
| 143 | if [ "$INSTALL_PACKAGES" = true ]; then |
| 144 | echo "π¦ Installing standard Clean Architecture packages..." |
| 145 | |
| 146 | add_package() { |
| 147 | local proj=$1 |
| 148 | local pkg=$2 |
| 149 | echo " + Adding $pkg..." |
| 150 | dotnet add "$proj" package "$pkg" > /dev/null 2>&1 |
| 151 | if [ $? -ne 0 ]; then |
| 152 | echo " β οΈ Failed to add $pkg. Check connection." |
| 153 | fi |
| 154 | } |
| 155 | |
| 156 | # Application Layer |
| 157 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "MediatR" |
| 158 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation" |
| 159 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "FluentValidation.DependencyInjectionExtensions" |
| 160 | add_package "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" "Microsoft.Extensions.Logging.Abstractions" |
| 161 | |
| 162 | # Infrastructure Layer |
| 163 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore" |
| 164 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "$DB_PACKAGE" |
| 165 | add_package "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" "Microsoft.EntityFrameworkCore.Design" |
| 166 | |
| 167 | # API Layer |
| 168 | add_package "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" "Microsoft.EntityFrameworkCore.Tools" |
| 169 | add_package "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" "Serilog.AspNetCore" |
| 170 | |
| 171 | # Test Projects |
| 172 | TEST_PROJECTS=( |
| 173 | "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" |
| 174 | "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" |
| 175 | "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" |
| 176 | ) |
| 177 | |
| 178 | for proj in "${TEST_PROJECTS[@]}"; do |
| 179 | add_package "$proj" "FluentAssertions" |
| 180 | add_package "$proj" "Moq" |
| 181 | done |
| 182 | |
| 183 | add_package "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" "Microsoft.AspNetCore.Mvc.Testing" |
| 184 | fi |
| 185 | |
| 186 | # --- 8. Final Verification --- |
| 187 | echo "ποΈ Verifying build..." |
| 188 | dotnet build |
| 189 | if [ $? -eq 0 ]; then |
| 190 | echo "β $PROJECT_NAME scaffolded successfully!" |
| 191 | else |
| 192 | echo "β οΈ Scaffolding finished, but build failed. Run 'dotnet restore' manually." |
| 193 | fi |