Azure DevOps Pipelines — Entry Angular Building Blocks
You are working on the CI/CD pipelines for this multi-library Angular workspace. Follow the conventions established in azure-pipelines.yml (publish pipeline) and deploy-demo.yml (demo app deployment pipeline).
Pipeline files
| File | Purpose |
|---|---|
azure-pipelines.yml |
Build, test, and publish all npm packages to npm registry. Triggered on master. |
deploy-demo.yml |
Build and deploy the demo app. |
pipeline-templates/install-node.yml |
Step template with the Node pin, consumed by both pipelines. |
Steps duplicated across both pipelines belong in pipeline-templates/ and are pulled in with
- template: pipeline-templates/<name>.yml (paths are relative to the repo root).
Versioning — MinVer
All packages are versioned via MinVer (a .NET tool that derives the version from git tags).
The version is computed once and stored in the pipeline variable theLatestVersion, then stamped into every library's package.json before the npm install step:
- task: PowerShell@2
displayName: 'Set MinVer Version'
inputs:
targetType: inline
script: |
$version = dotnet minver -p preview
echo "##vso[task.setvariable variable=theLatestVersion]$version"
echo "##vso[build.updatebuildnumber]$version"
When adding a new library, add a version-stamping step in the same block as the others — before the npm ci step:
- task: Npm@1
displayName: 'Set the version for <new-lib>'
inputs:
command: custom
customCommand: version $(theLatestVersion) --no-git-tag-version
workingDir: 'libs/<new-lib>'
The --no-git-tag-version flag prevents npm from creating a git tag during the version bump.
Stage structure
The publish pipeline has two stages with a hard dependency:
ci_build ──→ publish_npm
ci_build stage
Runs all verification before any artifacts are produced. Order of steps is significant:
checkout— withfetchDepth: 50andfetchTags: true(MinVer needs tag history)- template: pipeline-templates/install-node.yml— pins the Node version- Install and run MinVer — sets
theLatestVersion - Stamp version into each library
package.json npm ci— install from lockfilenpm run lint— lint all librariesnpm run build @enigmatry/entry-components— build libs in dependency ordernpm run build @enigmatry/entry-formnpm run automated-tests— SCSS theme compilation + scss-foundation testsFileTransform@2— substitute pipeline variables intodist/**/package.json(used to inject peer dependency versions)PublishBuildArtifacts@1— publishdist/as artifactPublishBuildArtifacts@1— publishlibs/as artifact
Build order matters:
@enigmatry/entry-componentsmust be built before@enigmatry/entry-formbecause entry-form depends on entry-components types.
Node version: the
UseNode@1pin lives inpipeline-templates/install-node.ymland must be kept in step with the Angular major's engine range (Angular 22 requires^22.22.3 || ^24.15.0 || >=26.0.0). Bump it together with three other places as part of every Angular upgrade:engines.nodein the rootpackage.json(the workspace/CI pin),engines.nodeinlibs/entry-components/package.jsonandlibs/entry-form/package.json(the full range consumers may use), and the@types/nodemajor in the rootpackage.json.
publish_npm stage
Runs as a deployment job targeting the npm environment (which has approval gates if configured):
- stage: publish_npm
dependsOn: ci_build
jobs:
- deployment: Deploy
environment: npm
strategy:
runOnce:
deploy:
steps:
- checkout: self
fetchDepth: 50
fetchTags: true
- download: current
displayName: 'Download build artifacts'
Each library is published with command: publish + useExternalRegistry pointing at the npm service connection. Working directories reference the artifact path:
workingDir: '$(Pipeline.Workspace)/$(artifactName)-$(Build.BuildNumber)/enigmatry/<lib-name>'
When adding a new library to publish, add a step that matches this pattern. The artifact sub-path depends on how ng-packagr places the output:
@enigmatry/*scoped packages →enigmatry/<lib-name>inside the artifact- Unscoped packages (
eslint-config,stylelint-config,scss-foundation) →<lib-name>directly
Variables
variables:
theLatestVersion: '' # set at runtime by MinVer
artifactName: entry-angular-building-blocks # base artifact name
peerDependencies.@enigmatry/entry-components: $(theLatestVersion) # used by FileTransform
- Add peer dependency variable entries for any new library whose peer deps should be auto-stamped.
- Variable names with dots (
peerDependencies.xxx) map to nested JSON paths viaFileTransform@2.
Trigger conventions
trigger:
- master # CI triggers only on master
pr: none # No PR builds (code review happens outside CI)
- Keep
pr: none— PR validation is not automated in this project. - Add path filters only if you have a strong reason to skip CI on certain paths.
Artifact naming
Artifacts follow the pattern $(artifactName)-$(Build.BuildNumber). The build number is set to the MinVer version via ##vso[build.updatebuildnumber], so artifacts are named like entry-angular-building-blocks-22.0.0.
Agent pool
Always use ubuntu-latest for both stages. Do not pin to a specific Ubuntu version unless a task requires it.
Task version guidelines
| Task | Current version used | Notes |
|---|---|---|
UseNode@1 |
v1 | Pins the Node version — must satisfy the Angular major's engine range. Lives in pipeline-templates/install-node.yml. Its input is version (semver range), not versionSpec. Do not use the older NodeTool@0. |
DotNetCoreCLI@2 |
v2 | For MinVer installation |
Npm@1 |
v1 | All npm operations |
PowerShell@2 |
v2 | MinVer version extraction |
FileTransform@2 |
v2 | JSON variable substitution |
PublishBuildArtifacts@1 |
v1 | Artifact publishing |
Do not upgrade task versions without testing — especially Npm@1 and FileTransform@2, which have breaking changes between major versions.
Adding a new library — full checklist
- Add a version-stamp step (
npm version $(theLatestVersion) --no-git-tag-version) beforenpm ci - Add a build step after existing build steps (respect dependency order)
- Add a
peerDependencies.<package-name>: $(theLatestVersion)variable if consumers need the version injected - Add a publish step in
publish_npmpointing at the correct artifact sub-path - Verify
FileTransform@2path covers the new library'spackage.json
Common mistakes to avoid
- Missing
fetchTags: trueon checkout — MinVer silently produces0.0.0-alpha.0without tag history. - Wrong artifact sub-path in publish step — causes "package not found" errors; check where ng-packagr places the output under
dist/. - Publishing before
dependsOn: ci_build— always gate publish on a successful build stage. - Using
npm installinstead ofnpm ci— always useciin pipelines to ensure reproducible installs from the lockfile. - Hardcoding version strings — always use
$(theLatestVersion)from MinVer, never hardcode.