Build and Publish Template Package #20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Build and Publish Template Package | |
on: | |
workflow_dispatch: | |
inputs: | |
version: | |
description: 'Package version (e.g., 1.0.0)' | |
required: true | |
default: '1.0.0' | |
author: | |
description: 'Template author' | |
required: true | |
default: 'Nitin Singh' | |
description: | |
description: 'Template description' | |
required: true | |
default: 'Full-stack Clean Architecture template with .NET 9 API and Angular 19' | |
jobs: | |
build-and-publish: | |
runs-on: windows-latest | |
env: | |
TEMPLATE_VERSION: ${{ github.event.inputs.version }} | |
TEMPLATE_AUTHOR: ${{ github.event.inputs.author }} | |
TEMPLATE_DESCRIPTION: ${{ github.event.inputs.description }} | |
NUGET_AUTH_TOKEN: ${{ secrets.NUGET_API_KEY }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
- name: Setup .NET | |
uses: actions/setup-dotnet@v3 | |
with: | |
dotnet-version: '9.0.x' | |
- name: Setup NuGet | |
uses: NuGet/setup-nuget@v1 | |
with: | |
nuget-version: '6.x' | |
- name: Display version | |
run: echo "Building template version ${{ env.TEMPLATE_VERSION }}" | |
- name: Create output directories | |
run: | | |
mkdir template-output | |
mkdir nupkg | |
- name: Run template preparation script | |
shell: pwsh | |
run: | | |
./template-src/CreateTemplate.ps1 -SourceDirectory . -TemplateNamespace Contact -OutputDirectory ./template-output | |
- name: Copy template.json file | |
shell: pwsh | |
run: | | |
# Create template config directory | |
New-Item -Path "./template-output/.template.config" -ItemType Directory -Force | |
# Copy the existing template.json file | |
Copy-Item -Path "./template-src/template.json" -Destination "./template-output/.template.config/template.json" | |
# Update metadata in template.json | |
$templateJsonPath = "./template-output/.template.config/template.json" | |
$templateJson = Get-Content -Path $templateJsonPath -Raw | ConvertFrom-Json | |
$templateJson.author = "${{ env.TEMPLATE_AUTHOR }}" | |
$templateJson | ConvertTo-Json -Depth 10 | Set-Content -Path $templateJsonPath | |
- name: Copy README template | |
shell: pwsh | |
run: | | |
# Ensure README is properly set up for NuGet package | |
Write-Host "Setting up README for NuGet package..." | |
# Check if README template exists in template-src | |
if (Test-Path "./template-src/README.template.md") { | |
# Copy as main README.md in the template output root | |
Copy-Item -Path "./template-src/README.template.md" -Destination "./template-output/README.md" | |
Write-Host "✅ README file set up successfully at template-output/README.md" | |
# Verify the file exists and has content | |
if (Test-Path "./template-output/README.md") { | |
$fileContent = Get-Content -Path "./template-output/README.md" -Raw | |
$contentLength = $fileContent.Length | |
Write-Host "README.md file size: $contentLength bytes" | |
} | |
else { | |
Write-Error "README.md was not copied correctly" | |
} | |
} | |
else { | |
Write-Error "README.template.md not found in template-src directory" | |
} | |
- name: Copy and update .nuspec file | |
shell: pwsh | |
run: | | |
# Copy the .nuspec file | |
Copy-Item -Path "./template-src/CleanArchitecture.FullStack.Template.nuspec" -Destination "./template-output/CleanArchitecture.FullStack.Template.nuspec" | |
# Update metadata in the .nuspec file | |
$nuspecPath = "./template-output/CleanArchitecture.FullStack.Template.nuspec" | |
$nuspecContent = Get-Content -Path $nuspecPath -Raw | |
# Replace version, author, and description with workflow inputs | |
$nuspecContent = $nuspecContent -replace '<version>.*?</version>', "<version>${{ env.TEMPLATE_VERSION }}</version>" | |
$nuspecContent = $nuspecContent -replace '<authors>.*?</authors>', "<authors>${{ env.TEMPLATE_AUTHOR }}</authors>" | |
$nuspecContent = $nuspecContent -replace '<description>.*?</description>', "<description>${{ env.TEMPLATE_DESCRIPTION }}</description>" | |
$nuspecContent = $nuspecContent -replace '<copyright>.*?</copyright>', "<copyright>Copyright © ${{ env.TEMPLATE_AUTHOR }} $((Get-Date).Year)</copyright>" | |
Set-Content -Path $nuspecPath -Value $nuspecContent | |
- name: Verify template structure | |
shell: pwsh | |
run: | | |
Write-Host "Verifying template structure..." | |
# Check that required files exist in template-output | |
$requiredFiles = @( | |
"docker-compose.yml", | |
".env-example", | |
"README.md" | |
) | |
foreach ($file in $requiredFiles) { | |
$filePath = "./template-output/$file" | |
if (Test-Path $filePath) { | |
Write-Host "✅ $file exists" | |
} | |
else { | |
Write-Host "❌ $file is missing" | |
# Create empty file if missing to prevent template validation errors | |
New-Item -Path $filePath -ItemType File -Force | |
} | |
} | |
# Check that required directories exist | |
$requiredDirs = @( | |
"backend", | |
"frontend" | |
) | |
foreach ($dir in $requiredDirs) { | |
$dirPath = "./template-output/$dir" | |
if (Test-Path $dirPath) { | |
Write-Host "✅ $dir directory exists" | |
} | |
else { | |
Write-Host "❌ $dir directory is missing" | |
# Create directory if missing | |
New-Item -Path $dirPath -ItemType Directory -Force | |
} | |
} | |
Write-Host "Template structure verification completed" | |
- name: Pack template | |
run: | | |
nuget pack ./template-output/CleanArchitecture.FullStack.Template.nuspec -OutputDirectory ./nupkg | |
- name: Verify template package contents | |
shell: pwsh | |
run: | | |
# Extract and verify the NuGet package contents to ensure README is included | |
$packagePath = Get-ChildItem -Path "./nupkg/*.nupkg" | Select-Object -First 1 -ExpandProperty FullName | |
$extractPath = "./nupkg-extracted" | |
Write-Host "Extracting NuGet package to verify contents..." | |
if (Test-Path $packagePath) { | |
Write-Host "Found package at: $packagePath" | |
# Create extract directory | |
if (Test-Path $extractPath) { | |
Remove-Item -Path $extractPath -Recurse -Force | |
} | |
New-Item -Path $extractPath -ItemType Directory -Force | Out-Null | |
# Simple direct extraction using Expand-Archive | |
$packageName = [System.IO.Path]::GetFileName($packagePath) | |
$packageBaseName = [System.IO.Path]::GetFileNameWithoutExtension($packagePath) | |
Write-Host "Copying package to .zip file for extraction" | |
$zipPath = [System.IO.Path]::Combine($extractPath, "$packageBaseName.zip") | |
Copy-Item -Path $packagePath -Destination $zipPath -Force | |
Write-Host "Extracting package from: $zipPath to: $extractPath" | |
Expand-Archive -Path $zipPath -DestinationPath "$extractPath/$packageBaseName" -Force | |
# Look for README in various locations | |
Write-Host "Searching for README.md in extracted package..." | |
$readmeFiles = Get-ChildItem -Path $extractPath -Filter "README.md" -Recurse | |
if ($readmeFiles.Count -gt 0) { | |
Write-Host "✅ README.md found at: $($readmeFiles[0].FullName)" | |
} | |
else { | |
# Check common locations where README might be | |
$commonLocations = @( | |
"$extractPath/$packageBaseName/README.md", | |
"$extractPath/$packageBaseName/content/README.md" | |
) | |
$foundReadme = $false | |
foreach ($location in $commonLocations) { | |
if (Test-Path $location) { | |
Write-Host "✅ README.md found at: $location" | |
$foundReadme = $true | |
break | |
} | |
} | |
if (-not $foundReadme) { | |
# List all files in extraction directory to help diagnose | |
Write-Host "Listing all extracted files:" | |
Get-ChildItem -Path $extractPath -Recurse | ForEach-Object { Write-Host $_.FullName } | |
Write-Error "❌ README.md is missing from the NuGet package" | |
} | |
} | |
} | |
else { | |
Write-Error "NuGet package not found at path: $packagePath" | |
} | |
- name: Test template package | |
shell: pwsh | |
run: | | |
dotnet new install ./nupkg/CleanArchitecture.FullStack.Template.${{ env.TEMPLATE_VERSION }}.nupkg | |
mkdir test-project | |
cd test-project | |
# Verify template installed correctly | |
dotnet new --list | findstr "cleanarch" | |
# Get full help to see available parameters | |
dotnet new cleanarch-fullstack --help | |
# Try with standard format first | |
$result = dotnet new cleanarch-fullstack --Organization TestCompany --ProjectName TestProject | |
# If that fails, try with -p: format | |
if (-not $?) { | |
Write-Host "Standard parameter format failed, trying -p: format..." | |
dotnet new cleanarch-fullstack -p:Organization=TestCompany -p:ProjectName=TestProject | |
} | |
# List files to verify creation | |
dir | |
- name: Test template package with organization | |
shell: pwsh | |
run: | | |
mkdir test-project-with-org | |
cd test-project-with-org | |
# Create project with fallback options | |
dotnet new cleanarch-fullstack --organization TestCompany --projectName TestProjectWithOrg | |
if (-not $?) { | |
Write-Host "Trying alternative parameter format" | |
dotnet new cleanarch-fullstack -p:Organization=TestCompany -p:ProjectName=TestProjectWithOrg | |
} | |
# List files to verify creation | |
dir | |
- name: Test template package without organization | |
run: | | |
mkdir test-project-no-org | |
cd test-project-no-org | |
# Create project with fallback options | |
dotnet new cleanarch-fullstack --projectName TestProjectNoOrg || ( | |
dotnet new cleanarch-fullstack -p:ProjectName=TestProjectNoOrg | |
) | |
# List files to verify creation | |
dir | |
- name: Test template package without Angular | |
run: | | |
mkdir test-project-no-angular | |
cd test-project-no-angular | |
# Create project with fallback options | |
dotnet new cleanarch-fullstack --organization TestCompany --projectName TestProjectNoAngular --includeAngular false || ( | |
dotnet new cleanarch-fullstack -p:Organization=TestCompany -p:ProjectName=TestProjectNoAngular -p:IncludeAngular=false | |
) | |
# Verify that frontend directory is not created | |
if (Test-Path "./frontend") { | |
Write-Error "Frontend directory should not exist when IncludeAngular is false" | |
exit 1 | |
} | |
# List files to verify creation | |
dir | |
- name: Test template package with default folder naming | |
shell: pwsh | |
run: | | |
# Test with organization using fallback syntax | |
dotnet new cleanarch-fullstack --Organization YourCompany --ProjectName TestProject | |
if (-not $?) { | |
Write-Host "Trying alternative parameter format" | |
dotnet new cleanarch-fullstack -p:Organization=YourCompany -p:ProjectName=TestProject | |
} | |
if (Test-Path "YourCompany.TestProject") { | |
Write-Host "✅ YourCompany.TestProject folder created successfully" | |
} | |
else { | |
Write-Error "❌ YourCompany.TestProject folder not created" | |
exit 1 | |
} | |
# Test without organization - should create folder TestProjectNoOrg | |
dotnet new cleanarch-fullstack --ProjectName TestProjectNoOrg | |
if (-not $?) { | |
Write-Host "Trying alternative parameter format" | |
dotnet new cleanarch-fullstack -p:ProjectName=TestProjectNoOrg | |
} | |
if (Test-Path "TestProjectNoOrg") { | |
Write-Host "✅ TestProjectNoOrg folder created successfully" | |
} | |
else { | |
Write-Error "❌ TestProjectNoOrg folder not created" | |
exit 1 | |
} | |
# Test with explicit output folder | |
dotnet new cleanarch-fullstack --Organization YourCompany --ProjectName TestProject --output CustomFolder | |
if (-not $?) { | |
Write-Host "Trying alternative parameter format" | |
dotnet new cleanarch-fullstack -p:Organization=YourCompany -p:ProjectName=TestProject -o CustomFolder | |
} | |
if (Test-Path "CustomFolder") { | |
Write-Host "✅ CustomFolder created successfully" | |
} | |
else { | |
Write-Error "❌ CustomFolder not created" | |
exit 1 | |
} | |
- name: Upload package artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: nuget-package | |
path: ./nupkg/*.nupkg | |
- name: Push to NuGet | |
run: | | |
dotnet nuget push ./nupkg/CleanArchitecture.FullStack.Template.${{ env.TEMPLATE_VERSION }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json |