Skip to content

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.yml

Build 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: 5

Build 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=false

Environment → Angular Configuration Mapping

Pipeline $(environment)Angular --configurationAPI Base URL
devdevelopmenthttps://gateway.microtec-test.com
stagestagehttps://gateway.microtecstage.com
preprodpreprodhttps://gateway.microtec-preprod.com
uatuathttps://gateway.microtec-uat.com
prodprodhttps://gateway.onlinemicrotec.com.sa

The Angular configuration maps directly to entries in angular.jsonprojects.{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 true

staticwebapp.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

OptimizationCurrent StatusImpact
Nx affected (skip unchanged apps)ActiveSaves 60–80% build time on small PRs
maxParallel: 5 in matrixActiveCaps agent usage; increase to 9 for faster full builds
--source-map=false in non-devActiveReduces bundle size by ~30%
--named-chunks=falseActiveBetter caching (content-hash only)
Angular build cache (ng cache)Not activeCould save 2–3 min on repeated builds

Troubleshooting

SymptomLikely CauseResolution
npm install fails with peer dependency errorsMissing --f flagVerify npm i --f in install step
Angular build fails: configuration not foundWrong --configuration valueCheck angular.json for supported configuration names
staticwebapp.config.json not in build outputNot listed in angular.json assetsAdd "src/staticwebapp.config.json" to assets array
SWA deploy fails: unauthorizedswaDeployToken expiredRegenerate in Azure Portal → SWA → Manage Deployment Token
Wrong API URL in deployed appEnvironment config mismatchVerify environment.{env}.ts API URL; check --configuration flag
All apps rebuild even on small changeShared lib changedExpected behavior — shared-lib changes affect all apps

Internal Documentation — Microtec Platform Team