Appearance
Angular SWA Build Pipeline
The Angular micro-frontend build pipeline compiles all 9 ERP Angular apps using Nx affected analysis, deploys static files to Azure Static Web Apps or Blob Storage, and updates Azure Front Door routing. Builds run in parallel using a matrix strategy.
Pipeline Location
Devops/azure/pipelines/frontApps/deploy/unified-frontend-pipeline.ymlBuild Flow
Dependency Installation
yaml
- task: NodeTool@0
displayName: 'Install Node 20'
inputs:
versionSpec: '20.x'
- task: Bash@3
displayName: 'Install npm dependencies'
inputs:
workingDirectory: FrontApps
script: |
npm i --f # --f (force) required: peer dependency conflicts in Angular 17--f Required
Angular 17 + PrimeNG 17 + some shared libraries have peer dependency version mismatches that cause npm install to fail without --f (force). This is a known issue tracked in the frontend backlog. Until resolved, all install commands must use --f.
Nx Affected Analysis
To avoid rebuilding apps that have not changed, the pipeline uses Nx's affected detection:
bash
# Determine which apps were affected by the commit
AFFECTED=$(npx nx show projects --affected --type=app --base=HEAD~1 --head=HEAD)
echo "Affected apps: $AFFECTED"
echo "##vso[task.setvariable variable=affectedApps]$AFFECTED"If the shared library (libs/shared-lib) changes, ALL apps are considered affected (Nx dependency graph).
In production releases, the --all flag is passed instead to force a full rebuild of every app, ensuring no stale cached bundle reaches production.
Parallel Build Matrix
All 9 apps build in parallel using Azure DevOps matrix strategy:
yaml
strategy:
matrix:
erp-home:
projectName: erp-home
port: '4401'
apps-accounting:
projectName: apps-accounting
port: '4402'
apps-hr:
projectName: apps-hr
port: '4403'
apps-finance:
projectName: apps-finance
port: '4404'
apps-sales:
projectName: apps-sales
port: '4405'
apps-purchase:
projectName: apps-purchase
port: '4406'
apps-inventory:
projectName: apps-inventory
port: '4407'
app-distribution:
projectName: app-distribution
port: '4408'
fixed-assets:
projectName: fixed-assets
port: '4409'
maxParallel: 5Build Command per Environment
yaml
- task: Bash@3
displayName: 'Build $(projectName) for $(environment)'
inputs:
workingDirectory: FrontApps
script: |
npx ng build $(projectName) \
--configuration=$(environment) \
--output-path=../dist/$(projectName) \
--source-map=false \
--named-chunks=false \
--output-hashing=all \
--stats-json=falseEnvironment → Angular Configuration Mapping
Pipeline $(environment) | Angular --configuration | API Base URL |
|---|---|---|
dev | development | https://gateway.microtec-test.com |
stage | stage | https://gateway.microtecstage.com |
preprod | preprod | https://gateway.microtec-preprod.com |
uat | uat | https://gateway.microtec-uat.com |
prod | prod | https://gateway.onlinemicrotec.com.sa |
The Angular configuration maps directly to entries in angular.json → projects.{name}.architect.build.configurations.
Artifact Publishing
After each matrix build job, the compiled output is published as a pipeline artifact:
yaml
- task: PublishBuildArtifacts@1
displayName: 'Publish dist artifacts'
inputs:
pathToPublish: 'dist/$(projectName)'
artifactName: 'frontend-$(projectName)-$(environment)'Artifacts are downloaded in the deploy stage and uploaded to SWA or Blob Storage.
Deploy Stage: Static Web Apps
yaml
- template: ../../templates/frontApps/deploy-swa.yml
parameters:
projectName: $(projectName)
environment: $(environment)
swaDeployToken: $(swaDeployToken)deploy-swa.yml template:
yaml
parameters:
- name: projectName
type: string
- name: environment
type: string
- name: swaDeployToken
type: string
steps:
- task: DownloadBuildArtifacts@1
inputs:
artifactName: 'frontend-${{ parameters.projectName }}-${{ parameters.environment }}'
downloadPath: '$(System.ArtifactsDirectory)'
- task: AzureStaticWebApp@0
displayName: 'Deploy ${{ parameters.projectName }} to SWA'
inputs:
azure_static_web_apps_api_token: ${{ parameters.swaDeployToken }}
app_location: '$(System.ArtifactsDirectory)/frontend-${{ parameters.projectName }}-${{ parameters.environment }}'
output_location: ''
skip_app_build: true
deployment_environment: ${{ parameters.environment }}
env:
AZURE_STATIC_WEB_APPS_API_TOKEN: ${{ parameters.swaDeployToken }}Deploy Stage: Blob Storage
For environments where Blob Storage hosting is used (some staging configurations):
yaml
- task: AzureCLI@2
displayName: 'Upload $(projectName) to Blob Storage'
inputs:
azureSubscription: 'mic-erp-$(environment)-sc'
script: |
STORAGE_ACCOUNT="micerpfr$(environment)sa"
# Upload with content hash caching headers
az storage blob upload-batch \
--account-name "$STORAGE_ACCOUNT" \
--destination "\$web/$(projectName)" \
--source "$(System.ArtifactsDirectory)/frontend-$(projectName)-$(environment)" \
--overwrite true \
--content-cache-control "max-age=31536000, immutable"
# Override index.html with no-cache (must always be fresh)
az storage blob upload \
--account-name "$STORAGE_ACCOUNT" \
--container-name "\$web" \
--name "$(projectName)/index.html" \
--file "$(System.ArtifactsDirectory)/frontend-$(projectName)-$(environment)/index.html" \
--content-cache-control "no-cache, no-store, must-revalidate" \
--overwrite truestaticwebapp.config.json Verification
Before deploying, the pipeline verifies the staticwebapp.config.json is present and valid:
bash
CONFIG_FILE="dist/$(projectName)/staticwebapp.config.json"
if [ ! -f "$CONFIG_FILE" ]; then
echo "ERROR: staticwebapp.config.json missing from build output"
echo "Add it to src/assets/ and include in angular.json assets array"
exit 1
fi
# Validate JSON syntax
python3 -c "import json, sys; json.load(open('$CONFIG_FILE'))" || {
echo "ERROR: staticwebapp.config.json is not valid JSON"
exit 1
}AFD Cache Purge
After all apps are deployed, the AFD cache is purged to ensure users see the new version:
yaml
- task: AzureCLI@2
displayName: 'Purge AFD Cache'
inputs:
azureSubscription: 'mic-erp-$(environment)-sc'
script: |
DOMAIN=$(get_domain "$(environment)")
az afd endpoint purge \
--resource-group "mic-erp-be-$(environment)-network-rg" \
--profile-name "mic-erp-afd" \
--endpoint-name "mic-erp-$(environment)-endpoint" \
--domains "app.${DOMAIN}" \
--content-paths '/*'Build Optimization Tips
| Optimization | Current Status | Impact |
|---|---|---|
| Nx affected (skip unchanged apps) | Active | Saves 60–80% build time on small PRs |
maxParallel: 5 in matrix | Active | Caps agent usage; increase to 9 for faster full builds |
--source-map=false in non-dev | Active | Reduces bundle size by ~30% |
--named-chunks=false | Active | Better caching (content-hash only) |
Angular build cache (ng cache) | Not active | Could save 2–3 min on repeated builds |
Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
npm install fails with peer dependency errors | Missing --f flag | Verify npm i --f in install step |
Angular build fails: configuration not found | Wrong --configuration value | Check angular.json for supported configuration names |
staticwebapp.config.json not in build output | Not listed in angular.json assets | Add "src/staticwebapp.config.json" to assets array |
SWA deploy fails: unauthorized | swaDeployToken expired | Regenerate in Azure Portal → SWA → Manage Deployment Token |
| Wrong API URL in deployed app | Environment config mismatch | Verify environment.{env}.ts API URL; check --configuration flag |
| All apps rebuild even on small change | Shared lib changed | Expected behavior — shared-lib changes affect all apps |
Related Documentation
- Static Web Apps — SWA configuration and naming
- Storage Accounts — Frontend Blob Storage hosting
- Azure Front Door — Cache purge and routing
- Orchestrators —
unified-frontend-pipeline.ymloverview