# Azure DevOps CI Pipeline
## 🔹 CI/CD Overview
✅ **CI (Continuous Integration)**
- Responsible for **Restore**, **Build**, **Test**, and **Package**
- Generates **deployable artifacts (`.zip`, `.dll`, `dist/`)**
✅ **CD (Continuous Deployment)**
- Responsible for **Deploying** applications to **Azure Web App** or **Azure Static Web Apps**
- Uses **Azure DevOps Pipelines** to automatically download and deploy applications from **artifacts**
---
## 🔹 CI/CD Stages
| **Stage** | **Task** | **Description** |
|------------------|-----------------|-----------|
| **Restore** | `dotnet restore` / `npm install` | Restores NuGet/npm dependencies |
| **Build** | `dotnet build` / `npm run build` | Compiles source code |
| **Test** | `dotnet test` / `npm test` | Runs unit tests|
| **Code Analysis** | `SonarQube` / `ESLint` | Analyzes code quality |
| **Publish** | `Publish Artifacts` | Generates .zip / .dll for deployment |
---
## 🔹 What is an Azure DevOps Agent?
### Agent Definition
- **An agent is a virtual machine (VM) or container that executes Azure DevOps CI/CD Pipelines.**
- **It downloads source code and runs `dotnet build`, `npm install`, `NuGet restore`**, etc.
### Agent offer by Microsoft Azure DevOps
- **`ubuntu-latest`** (Linux)
- **`windows-latest`** (Windows)
- **`macos-latest`** (Mac)
### Self-hosted Agent
- Can be installed on **On-Premises Servers** or **Docker Containers**
- Suitable for **private network deployments**
---
## 🔹 SDK & NuGet
### When to Use `UseDotNet@2` & `NodeTool@0`?
- **When the agent does not have `.NET SDK` or `Node.js` pre-installed**
- **`ubuntu-latest` & `windows-latest`agents include .NET SDK by default**
- **`ubuntu-latest`** **includes Node.js by default**
- Required for executing `dotnet build`, `dotnet publish`, `npm install`, `npm run build`, etc.
#### .NET Framework (NuGetToolInstaller@1)
- Required for restoring package.config or *.sln
```yaml!
- task: NuGetToolInstaller@1
displayName: 'Install NuGet'
inputs:
versionSpec: '5.4.0'
- task: NuGetCommand@2
displayName: 'Restore NuGet Packages'
inputs:
command: 'restore'
restoreSolution: 'MyProject.sln'
```
#### .NET Core (.NET 5 ⬆) (DotNetCoreCLI@2)
- `dotnet restore` automatically restores NuGet packages
```yaml!
- task: DotNetCoreCLI@2
displayName: 'Restore NuGet Packages'
inputs:
command: 'restore'
projects: '**/*.csproj'
```
- Private NuGet Source
- Manually install NuGet for private repositories like Nexus
```yaml!
- task: NuGetCommand@2
displayName: 'Restore NuGet Packages from Nexus'
inputs:
command: 'restore'
restoreSolution: 'MyProject.sln'
feedsToUse: 'select'
vstsFeed: 'https://nexus.example.com/repository/nuget-hosted'
```
#### Vite + React
- Use --legacy-peer-deps to avoid version conflicts in peer dependencies
```yaml!
- script: |
npm install
displayName: 'Install Dependencies'
```
## 🔹 Build Stage
### .NET Framework
- **VSBuild@1** (Windows Only)
```yaml!
- task: VSBuild@1
displayName: 'Build .NET Framework Solution'
inputs:
solution: '**/*.sln'
msbuildArgs: '/p:Configuration=Release'
platform: 'Any CPU'
configuration: 'Release'
```
### .NET Core 5+
- **DotNetCoreCLI@2** - Supports `Windows/Linux/Mac`
```yaml!
- task: DotNetCoreCLI@2
displayName: 'Build .NET Core Application'
inputs:
command: 'build'
arguments: '--configuration Release'
```
### Vite + React
```yaml!
- script: |
npm run build
displayName: 'Build Vite React App'
```
## 🔹 Publish Stage
### .NET Framework
- **VSBuild@1** **(MSBuild + WebDeploy for IIS)**
```yaml!
- task: VSBuild@1
displayName: 'Build .NET Framework Solution'
inputs:
solution: '**/*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:PublishProfile=WebDeploy'
platform: 'Any CPU'
configuration: 'Release'
```
### .NET Core
#### Azure App Service / Linux
- **DotNetCoreCLI@2** (dotnet publish) -> **PublishBuildArtifacts@1**
```yaml!
- task: DotNetCoreCLI@2
displayName: 'Publish .NET Core API'
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: true
# Save drop to Artifact
- task: PublishBuildArtifacts@1
displayName: 'Store Published Artifacts'
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
```
#### Azure Kubernetes
### Vite + React
- Between stages, files are not automatically preserved. Save them in an **artifact**
```yaml!
- task: CopyFiles@2
inputs:
SourceFolder: 'dist'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
displayName: 'Copy Built Files to Staging Directory'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'dist'
displayName: 'Publish Build Artifacts'
```
## 🔹 Deployment Stage
### IIS (.NET Framework)
```yaml!
- task: VSBuild@1
displayName: 'Build .NET Framework Solution'
inputs:
solution: '**/*.sln'
msbuildArgs: '/p:DeployOnBuild=true /p:PublishProfile=WebDeploy'
platform: 'Any CPU'
configuration: 'Release'
```
### Azure Web App (.NET Core)
#### Web App Service
- Use **AzureWebApp@1** to deploy
- Support dotnet publish deploy by .zip
```yaml!
- task: AzureWebApp@1
displayName: 'Deploy .NET Core API'
inputs:
azureSubscription: 'Azure Service Connection'
appName: 'app-service-name'
package: '$(Build.ArtifactStagingDirectory)'
```
### Azure Static Web App (Vite + React)
#### Static Web App Service
- Use **AzureStaticWebApp@0** to deploy
```yaml!
- stage: Deploy
jobs:
- deployment: Deploy
environment: 'Production'
strategy:
runOnce:
deploy:
steps:
# Download to Pipeline.WorkSpace directory
- task: DownloadBuildArtifacts@0
inputs:
artifactName: 'dist'
downloadPath: '$(Pipeline.Workspace)'
displayName: 'Download dist Artifact'
- task: AzureStaticWebApp@0
inputs:
app_location: 'dist' # Path where we find the source code
skip_app_build: true # no need for npm build & install
skip_api_build: true # without api
output_location: '' # Path where the exit is located
azure_static_web_apps_api_token: $(SWA_DEPLOYMENT_TOKEN) #部署權杖
```
## 🔹 Common Issues & Fixes
### 🔍Issue : `There was an error exporting the HTTPS developer certificate to a file. Please create the target directory before exporting.`
#### ❓ Problem : .NET 8 does not automatically create the certificate folder, causing HTTPS certificate export to fail.
#### ✅ Fixed : Manually create the folder before exporting the certificate.
```yaml!
- script: |
mkdir -p ~/.aspnet/https # Linux
displayName: 'Fix HTTPS Developer Certificate'
```
### 🔍Issue : `AzureStaticWebApp@0 App Directory Location: 'dist' is invalid. Could not detect this directory.`
#### ❓ Problem : The dist artifact is downloaded to `/home/vsts/work/1`, but AzureStaticWebApp@0 searches in `/home/vsts/work/1/s`.
#### ✅ Fixed : Move the dist folder to the correct location.
```yaml!
- script: |
mv $(Pipeline.Workspace)/dist $(Pipeline.Workspace)/s/dist
displayName: 'Move dist to Source Directory'
```