Остання активність 1 month ago

Версія 939dd9a14c3fae95b35b5eeeaf94c4570742bfd3

README.md Неформатований

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 }
dotnet-10-clean-architecture-boilerplate-guide.md Неформатований

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 — provides WebApplicationFactory<Program> for in-process HTTP testing

Test File Naming Convention

Test files follow the pattern {ClassUnderTest}Tests.cs. For example:

  • ResultTests.cs tests Result.cs
  • ValidationBehaviorTests.cs tests ValidationBehavior.cs

Project References

Each test project references only the layer it tests:

  • Domain.TestsDomain
  • Application.TestsApplication (which transitively includes Domain)
  • IntegrationTestsApi (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:

  1. Standard CI (test, SonarCloud, Qodana) — reusable as-is across projects. Just update repository variables.
  2. 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-by trailers
  • Build & run commandsdotnet 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 DomainEventInterceptor to dispatch domain events in-process during SaveChanges. 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:

  1. No guarantee of delivery to external systems — if the application crashes after SaveChanges but before an external message is sent, the event is lost.
  2. 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:

  1. Handler modifies entity + raises domain event
  2. SaveChanges interceptor serializes domain events into OutboxMessages table (same transaction)
  3. Transaction commits — business data and outbox messages are atomically consistent
  4. 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.

scaffold.ps1 Неформатований
1param(
2 [string]$ProjectName,
3 [ValidateSet("slnx", "sln")]
4 [string]$Format = "slnx",
5 [switch]$SkipPackages = $false
6)
7
8# --- 1. Setup & Validation ---
9if ([string]::IsNullOrWhiteSpace($ProjectName)) {
10 $ProjectName = Read-Host "Please enter a project name"
11}
12
13if ([string]::IsNullOrWhiteSpace($ProjectName)) {
14 Write-Host "❌ Name required." -ForegroundColor Red; exit 1
15}
16
17if (Test-Path $ProjectName) {
18 Write-Host "❌ Directory '$ProjectName' already exists. Aborting to prevent overwrite." -ForegroundColor Red; exit 1
19}
20
21$SlnFile = "$ProjectName.$Format"
22Write-Host "🚀 Scaffolding Clean Architecture (DDD) for: $ProjectName" -ForegroundColor Cyan
23
24# Create Root Directory
25New-Item -ItemType Directory -Path $ProjectName | Out-Null
26Set-Location $ProjectName
27
28# --- 2. Smart SDK Detection ---
29$LatestSdk = dotnet --list-sdks | Select-Object -Last 1 | ForEach-Object { $_.Split(' ')[0] }
30
31if ($LatestSdk) {
32 Write-Host "ℹ️ Detected SDK: $LatestSdk. Pinning global.json..." -ForegroundColor Gray
33 dotnet new globaljson --sdk-version $LatestSdk --roll-forward latestFeature
34}
35
36dotnet new gitignore
37
38# --- 3. Create Solution & Fix NuGet ---
39if ($Format -eq "slnx") { dotnet new sln -n $ProjectName --format slnx }
40else { dotnet new sln -n $ProjectName }
41
42Write-Host "📦 Configuring NuGet sources..." -ForegroundColor Cyan
43dotnet new nugetconfig --force
44$CurrentSources = dotnet nuget list source --configfile "nuget.config"
45if ($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 ---
50Write-Host "🔨 Creating projects..." -ForegroundColor Cyan
51
52# Source Projects
53dotnet new classlib -n "$ProjectName.Domain" -o "src/$ProjectName.Domain"
54dotnet new classlib -n "$ProjectName.Application" -o "src/$ProjectName.Application"
55dotnet new classlib -n "$ProjectName.Infrastructure" -o "src/$ProjectName.Infrastructure"
56dotnet new webapi -n "$ProjectName.Api" -o "src/$ProjectName.Api" --use-controllers
57
58# Test Projects
59dotnet new xunit -n "$ProjectName.Domain.Tests" -o "tests/$ProjectName.Domain.Tests"
60dotnet new xunit -n "$ProjectName.Application.Tests" -o "tests/$ProjectName.Application.Tests"
61dotnet new xunit -n "$ProjectName.IntegrationTests" -o "tests/$ProjectName.IntegrationTests"
62
63# --- 4.1 CLEANUP BOILERPLATE ---
64Write-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
78foreach ($File in $FilesToRemove) {
79 if (Test-Path $File) {
80 Remove-Item $File -Force
81 }
82}
83
84# --- 5. Add to Solution ---
85Write-Host "📂 Organizing solution structure..." -ForegroundColor Cyan
86
87# Add Src
88dotnet sln $SlnFile add "src/$ProjectName.Domain/$ProjectName.Domain.csproj" -s "src"
89dotnet sln $SlnFile add "src/$ProjectName.Application/$ProjectName.Application.csproj" -s "src"
90dotnet sln $SlnFile add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" -s "src"
91dotnet sln $SlnFile add "src/$ProjectName.Api/$ProjectName.Api.csproj" -s "src"
92
93# Add Tests
94dotnet sln $SlnFile add "tests/$ProjectName.Domain.Tests/$ProjectName.Domain.Tests.csproj" -s "tests"
95dotnet sln $SlnFile add "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj" -s "tests"
96dotnet sln $SlnFile add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" -s "tests"
97
98# --- 6. Add References ---
99Write-Host "🔗 Wiring up dependencies..." -ForegroundColor Cyan
100
101# Application -> Domain
102dotnet add "src/$ProjectName.Application/$ProjectName.Application.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj"
103
104# Infrastructure -> Application & Domain
105dotnet add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj"
106dotnet add "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj"
107
108# API -> Application & Infrastructure
109dotnet add "src/$ProjectName.Api/$ProjectName.Api.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj"
110dotnet add "src/$ProjectName.Api/$ProjectName.Api.csproj" reference "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj"
111
112# Tests
113dotnet add "tests/$ProjectName.Domain.Tests/$ProjectName.Domain.Tests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj"
114
115dotnet 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
117dotnet add "tests/$ProjectName.Application.Tests/$ProjectName.Application.Tests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj"
118
119dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Api/$ProjectName.Api.csproj"
120dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Infrastructure/$ProjectName.Infrastructure.csproj"
121dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Application/$ProjectName.Application.csproj"
122dotnet add "tests/$ProjectName.IntegrationTests/$ProjectName.IntegrationTests.csproj" reference "src/$ProjectName.Domain/$ProjectName.Domain.csproj"
123
124# --- 7. Install Nuget Packages (Optional) ---
125if (-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 ---
163Write-Host "🏗️ Verifying build..." -ForegroundColor Cyan
164dotnet build
165if ($LASTEXITCODE -eq 0) {
166 Write-Host "$ProjectName scaffolded successfully!" -ForegroundColor Green
167 Write-Host " 👉 cd $ProjectName" -ForegroundColor Gray
168}
scaffold.sh Неформатований
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 ---
7if [ -z "$1" ]; then
8 echo "❌ Please provide a project name."
9 echo "Usage: ./scaffold.sh MyProjectName [slnx|sln]"
10 exit 1
11fi
12
13PROJECT_NAME=$1
14FORMAT=${2:-slnx}
15INSTALL_PACKAGES=${3:-true} # Default to true
16SLN_FILE="$PROJECT_NAME.$FORMAT"
17
18# Check if directory exists to avoid overwriting
19if [ -d "$PROJECT_NAME" ]; then
20 echo "❌ Directory '$PROJECT_NAME' already exists. Aborting."
21 exit 1
22fi
23
24echo "🚀 Scaffolding $FORMAT solution for: $PROJECT_NAME (Clean Architecture + DDD)"
25
26# Create Root Directory and enter it
27mkdir "$PROJECT_NAME"
28cd "$PROJECT_NAME" || exit
29
30# --- 2. Smart SDK Detection ---
31# Get the latest installed SDK version (e.g. 10.0.102)
32LATEST_SDK=$(dotnet --list-sdks | tail -n 1 | awk '{print $1}')
33
34if [ -n "$LATEST_SDK" ]; then
35 echo "ℹ️ Detected SDK: $LATEST_SDK. Pinning global.json..."
36 dotnet new globaljson --sdk-version "$LATEST_SDK" --roll-forward latestFeature
37else
38 echo "⚠️ No SDK detected. Skipping global.json."
39fi
40
41dotnet new gitignore
42
43# --- 3. Create Solution & Fix NuGet ---
44if [ "$FORMAT" == "slnx" ]; then
45 dotnet new sln -n "$PROJECT_NAME" --format slnx
46else
47 dotnet new sln -n "$PROJECT_NAME"
48fi
49
50# [FIX] Create a local nuget.config to ensure we can find packages
51echo "📦 Configuring NuGet sources..."
52dotnet new nugetconfig --force
53
54# Check if nuget.org is already there
55if ! 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"
57fi
58
59# --- 4. Create Projects ---
60echo "🔨 Creating projects..."
61dotnet new classlib -n "$PROJECT_NAME.Domain" -o "src/$PROJECT_NAME.Domain"
62dotnet new classlib -n "$PROJECT_NAME.Application" -o "src/$PROJECT_NAME.Application"
63dotnet new classlib -n "$PROJECT_NAME.Infrastructure" -o "src/$PROJECT_NAME.Infrastructure"
64dotnet new webapi -n "$PROJECT_NAME.Api" -o "src/$PROJECT_NAME.Api" --use-controllers
65
66mkdir -p tests
67dotnet new xunit -n "$PROJECT_NAME.Domain.Tests" -o "tests/$PROJECT_NAME.Domain.Tests"
68dotnet new xunit -n "$PROJECT_NAME.Application.Tests" -o "tests/$PROJECT_NAME.Application.Tests"
69dotnet new xunit -n "$PROJECT_NAME.IntegrationTests" -o "tests/$PROJECT_NAME.IntegrationTests"
70
71# --- 4.1 CLEANUP BOILERPLATE ---
72echo "🧹 Removing default template files..."
73
74# Define array of files to remove relative to solution root
75FILES_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
87for file in "${FILES_TO_REMOVE[@]}"; do
88 if [ -f "$file" ]; then
89 rm "$file"
90 echo " - Deleted $file"
91 fi
92done
93
94# --- 5. Add to Solution (With Visual Folders) ---
95echo "📂 Organizing solution structure..."
96# Source
97dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" -s "src"
98dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" -s "src"
99dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" -s "src"
100dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" -s "src"
101
102# Tests
103dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" -s "tests"
104dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" -s "tests"
105dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" -s "tests"
106
107# --- 6. Add References ---
108echo "🔗 Wiring up dependencies..."
109
110# Application -> Domain
111dotnet add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
112
113# Infrastructure -> Application AND Domain
114dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
115dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
116
117# API -> Application AND Infrastructure
118dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
119dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj"
120
121# Tests
122dotnet add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
123
124dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
125dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
126
127dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj"
128dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj"
129dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
130dotnet 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) ---
133if [ "$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"
174fi
175
176# --- 8. Final Verification ---
177echo "🏗️ Verifying build..."
178dotnet build
179if [ $? -eq 0 ]; then
180 echo "$PROJECT_NAME scaffolded successfully!"
181 echo "👉 cd $PROJECT_NAME"
182else
183 echo "⚠️ Scaffolding finished, but build failed. Run 'dotnet restore' manually."
184fi
scaffold_project.sh Неформатований
1#!/bin/bash
2
3# --- 0. Determine Project Name ---
4# Grab the name of the current directory
5PROJECT_NAME=$(basename "$PWD")
6
7echo "📂 Using current directory name as project name: $PROJECT_NAME"
8
9# --- 1. Configuration ---
10FORMAT="slnx" # Options: "sln" or "slnx"
11INSTALL_PACKAGES=true # Set to true to install NuGet packages
12
13echo "🚀 Initializing $PROJECT_NAME (Format: $FORMAT)..."
14
15# --- 1.5 Database Selection ---
16echo ""
17echo "🗄️ Select your Database Provider for $PROJECT_NAME:"
18echo " 1) PostgreSQL (Npgsql) [Default]"
19echo " 2) SQL Server"
20echo " 3) SQLite"
21# < /dev/tty allows interactive prompt when piping via curl
22read -p "Enter choice [1-3] (Default: 1): " DB_CHOICE < /dev/tty
23
24case $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 ;;
37esac
38echo ""
39
40# --- 2. Smart SDK Detection ---
41# Get the latest installed SDK version
42LATEST_SDK=$(dotnet --list-sdks | tail -n 1 | awk '{print $1}')
43
44if [ -n "$LATEST_SDK" ]; then
45 echo "ℹ️ Detected SDK: $LATEST_SDK. Pinning global.json..."
46 dotnet new globaljson --sdk-version "$LATEST_SDK" --roll-forward latestFeature
47else
48 echo "⚠️ No SDK detected. Skipping global.json."
49fi
50
51dotnet new gitignore
52
53# --- 3. Create Solution & Fix NuGet ---
54if [ "$FORMAT" = "slnx" ]; then
55 echo "📄 Creating .slnx solution..."
56 dotnet new sln -n "$PROJECT_NAME" --format slnx
57 SLN_FILE="$PROJECT_NAME.slnx"
58else
59 echo "📄 Creating standard .sln solution..."
60 dotnet new sln -n "$PROJECT_NAME"
61 SLN_FILE="$PROJECT_NAME.sln"
62fi
63
64# Create a local nuget.config to ensure we can find packages
65echo "📦 Configuring NuGet sources..."
66dotnet new nugetconfig --force
67
68# Check if nuget.org is already there, if not, add it
69if ! 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"
71fi
72
73# --- 4. Create Projects ---
74echo "🔨 Creating projects..."
75dotnet new classlib -n "$PROJECT_NAME.Domain" -o "src/$PROJECT_NAME.Domain"
76dotnet new classlib -n "$PROJECT_NAME.Application" -o "src/$PROJECT_NAME.Application"
77dotnet new classlib -n "$PROJECT_NAME.Infrastructure" -o "src/$PROJECT_NAME.Infrastructure"
78dotnet new webapi -n "$PROJECT_NAME.Api" -o "src/$PROJECT_NAME.Api" --use-controllers
79
80mkdir -p tests
81dotnet new xunit -n "$PROJECT_NAME.Domain.Tests" -o "tests/$PROJECT_NAME.Domain.Tests"
82dotnet new xunit -n "$PROJECT_NAME.Application.Tests" -o "tests/$PROJECT_NAME.Application.Tests"
83dotnet new xunit -n "$PROJECT_NAME.IntegrationTests" -o "tests/$PROJECT_NAME.IntegrationTests"
84
85# --- 4.1 CLEANUP BOILERPLATE ---
86echo "🧹 Removing default template files..."
87
88FILES_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
100for file in "${FILES_TO_REMOVE[@]}"; do
101 if [ -f "$file" ]; then
102 rm "$file"
103 echo " - Deleted $file"
104 fi
105done
106
107# --- 5. Add to Solution (With Visual Folders) ---
108echo "📂 Organizing solution structure in $SLN_FILE..."
109
110dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj" -s "src"
111dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" -s "src"
112dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" -s "src"
113dotnet sln "$SLN_FILE" add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" -s "src"
114
115dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" -s "tests"
116dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" -s "tests"
117dotnet sln "$SLN_FILE" add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" -s "tests"
118
119# --- 6. Add References ---
120echo "🔗 Wiring up dependencies..."
121
122# Clean Architecture Flow
123dotnet add "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
124
125dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
126dotnet add "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
127
128dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
129dotnet add "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj"
130
131# Test References
132dotnet add "tests/$PROJECT_NAME.Domain.Tests/$PROJECT_NAME.Domain.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
133
134dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
135dotnet add "tests/$PROJECT_NAME.Application.Tests/$PROJECT_NAME.Application.Tests.csproj" reference "src/$PROJECT_NAME.Domain/$PROJECT_NAME.Domain.csproj"
136
137dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Api/$PROJECT_NAME.Api.csproj"
138dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Infrastructure/$PROJECT_NAME.Infrastructure.csproj"
139dotnet add "tests/$PROJECT_NAME.IntegrationTests/$PROJECT_NAME.IntegrationTests.csproj" reference "src/$PROJECT_NAME.Application/$PROJECT_NAME.Application.csproj"
140dotnet 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) ---
143if [ "$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"
184fi
185
186# --- 8. Final Verification ---
187echo "🏗️ Verifying build..."
188dotnet build
189if [ $? -eq 0 ]; then
190 echo "$PROJECT_NAME scaffolded successfully!"
191else
192 echo "⚠️ Scaffolding finished, but build failed. Run 'dotnet restore' manually."
193fi