SPFx Azure DevOps Pipeline: Increment version, push to repository and publish package
Create an Azure DevOps Pipeline to increment the version, create a tag for that version and check it into the repository and then package the solution
Table of contents
- The idea is quite simple
- Here we go
- The yaml file
- Step 1: Checkout
- Step 2: Set Git user
- Step 3: PowerShell to set the output variables
- Step 4: Git checkout
- Step 5: Use Node.js
- Step 6: npm install
- Step 7: gulp clean
- Step 8 gulp bump-version
- Step 9: gulp bundle --ship
- Step 10: gulp package-solution --ship
- Step 11: Read updated version from package.json
- Step 12: push back to Repository
- Step 13: Create Git tag
- Step 14: Copy package file(s)
- Step 15: Publish Artifacts
- The complete file
- The yaml file
- Result
Last week I published a blog post describing how to increment the version of the SPFx solution using a gulp task
. After that, people asked in the comments how to map the whole thing in an Azure DevOps pipeline so that the pipeline automatically increments the version and writes this updated version back to the repository. A very good question, I think, which I actually wanted to answer later (for myself). So far I only had the "normal" pipeline that publishes the package. Now the challenge came a bit earlier than I thought.
The idea is quite simple
When pushing to main/master branch, the Azure DevOps pipeline should automatically check out the branch, increment the version, build the package, check the changes back into the repository and (optionally) create a Git tag with the version number to make it clearer. You can turn off tagging or version incrementing in the pipeline. In addition, you can use the commit message to determine which part of the version (major, minor or patch) should be updated.
Here we go
Before you start with the pipeline and the .yaml
file, you have to configure something. In the project settings under Repository
=> {NameOfRepository}
=> Security
=> USER: {NameOfRepository} Build Service
. This user must be given the permissions Read
, Contribute
, Create tag
, Create branch
and Contribute to pull requests
.
The yaml
file
I won't show you how to create a pipeline but will go into the individual steps in the yaml
file. If that doesn't interest you, you can jump straight to the final file and use it.
Step 1: Checkout
- checkout: self
clean: true
persistCredentials: true
This step determines how the pipeline should check out the source code.
Step 2: Set Git user
- script: |
git config --global user.email devops@spfx-app.dev & git config --global user.name "spfx-app.dev".
workingDirectory: $(System.DefaultWorkingDirectory)
This sets the user to be displayed at (code) checkin/push. The user does not have to actually exist. It will only be displayed in Azure DevOps as specified.
Step 3: PowerShell to set the output variables
As mentioned at the beginning, the version should only be incremented if it is desired (pipeline variable CI_BUMP_VERSION
is set to TRUE
). In addition, the commit message Build.SourceVersionMessage
can be used to determine which part of the version (major
, minor
, or patch
) should be incremented. By default, the patch version is always incremented.
- powershell: |
$commitMsg = "$(Build.SourceVersionMessage)"
Write-Host $commitMsg
Write-Host "Version bump is ENABLED"
$bumpVersionArgs = "--no-patch"
Write-Host "##vso[task.setvariable variable=BUMP_MAJOR_VERSION;]$false"
Write-Host "##vso[task.setvariable variable=BUMP_MINOR_VERSION;]$false"
Write-Host "##vso[task.setvariable variable=BUMP_PATCH_VERSION;]$false"
if($commitMsg -match "(major):.*") {
Write-Host "Bump Major Version"
Write-Host "##vso[task.setvariable variable=BUMP_MAJOR_VERSION;]$true"
$bumpVersionArgs = "--major"
}
elseif($commitMsg -match "(minor):.*") {
Write-Host "Bump Minor Version"
Write-Host "##vso[task.setvariable variable=BUMP_MINOR_VERSION;]$true"
$bumpVersionArgs = "--minor"
}
else {
Write-Host "Bump Patch Version"
Write-Host "##vso[task.setvariable variable=BUMP_PATCH_VERSION;]$true"
$bumpVersionArgs = "--patch"
}
Write-Host "##vso[task.setvariable variable=BUMP_VERSION_ARGS;]$bumpVersionArgs"
displayName: Get the commit message
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
If the commit message contains a minor:
the minor version is incremented. If the commit message contains a major:
the major version is incremented. It is not necessary to specify patch:
as it will increment the patch version by default, but this message would increment the patch version. Depending on which version part is updated, the argument for the later gulp bump
command is stored in the variable BUMP_VERSION_ARGS
to be passed in step 8.
Note
: for a pull request, the "Title" column of the form in the UI corresponds to the variable Build.SourceVersionMessage
.
Step 4: Git checkout
- script: |
git checkout $(Build.SourceBranch)
displayName: Checkout Branch
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
This script checks out the source branch. Again, this step is only executed if the condition is true: The variable CI_BUMP_VERSION
is equal to TRUE
.
Step 5: Use Node.js
- task: NodeTool@0
displayName: 'Use Node 16.x'
inputs:
versionSpec: 16.x
checkLatest: true
I think it is clear what this step does. Node version 10.x will be installed.
Note
: Of course you have to adapt the version of Node to your SPFx version. A list of supported Node versions can be found here
Step 6: npm install
- task: Npm@1
displayName: 'npm install'
inputs:
workingDir: ' $(Build.Repository.LocalPath)'
verbose: true
All npm
packages will now be installed.
Step 7: gulp clean
- task: gulp@0
displayName: 'gulp clean'
inputs:
targets: clean
The gulp
task gulp clean
is executed.
Step 8 gulp bump-version
Now the task I described in the last blog post is used to increment the version. Again, only if the pipeline variable CI_BUMP_VERSION
has been set to TRUE
.
- task: gulp@0
displayName: 'gulp bump-version'
inputs:
targets: 'bump-version'
arguments: $(BUMP_VERSION_ARGS)
continueOnError: true
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
The stored value from the variable BUMP_VERSION_ARGS
from step 3 is passed as an argument (--major
, --minor
or --patch
).
Step 9: gulp bundle --ship
The gulp
task bump-version
is normally executed automatically before the gulp bundle --ship
task (see my blog post). But as you can tell the pipeline whether the version should be incremented or not, I had to split this task and pass the additional argument --no-ship
to the gulp bundle --ship
so that the bump-version
task is not executed again.
- task: gulp@0
displayName: 'gulp bundle --ship --no-patch'
inputs:
targets: bundle
arguments: '--ship --no-patch'
continueOnError: true
Step 10: gulp package-solution --ship
I think this step is self-explanatory. The solution is packed.
- task: gulp@0
displayName: 'gulp package-solution --ship'
inputs:
targets: 'package-solution'
arguments: '--ship'
Step 11: Read updated version from package.json
If the version was updated, it must of course be read again (for the later Git tag and also the commit message). I did this with a PowerShell script.
- powershell: |
$newVersion = (Get-content ./package.json| out-string | ConvertFrom-Json).version
Write-Host "##vso[task.setvariable variable=NEW_VERSION;]$newVersion"
Write-Host $newVersion
displayName: Get Version from package.json and set in variable
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
The package.json
file is read and the version number is stored in the variable NEW_VERSION
.
Step 12: push back to Repository
Now the version is restored to the repository if the version has been updated.
- script: |
echo $(NEW_VERSION)
git commit -a -m "[skip ci] Version updated to $(NEW_VERSION)"
git push
displayName: Push changes to master
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
All changes are checked in with the message [skip ci] version updated to $(NEW_VERSION)
. The [skip ci]
is very important. In case you have configured an automatic execution of the pipeline on the (main/master) branch, it would execute the pipeline again (continuous loop). This can be prevented with this text in the commit message (see: docs.microsoft.com/en-us/azure/devops/pipel..).
Step 13: Create Git tag
I thought it was helpful that a Git tag is created at the same time as the version update and after the check in. The name of the tag is then the version number. This is only done if the variables CI_BUMP_VERSION
and CI_CREATE_GIT_TAG
are both set to TRUE
.
- script: |
git tag $(NEW_VERSION) HEAD
git push --tags
displayName: Create Tag for last commit version
workingDirectory: $(System.DefaultWorkingDirectory)
condition: and(eq(variables['CI_BUMP_VERSION'], 'TRUE'), eq(variables['CI_CREATE_GIT_TAG'], 'TRUE'))
Step 14: Copy package file(s)
Now the .sppkg
files are copied into the temporary drop
folder.
- task: CopyFiles@2
displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)/drop'
inputs:
Contents: '**/*.sppkg'
TargetFolder: '$(Build.ArtifactStagingDirectory)/drop'
Step 15: Publish Artifacts
And finally, the artifacts have to be published so that the .sppkg
files can be downloaded.
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
That was it (almost)
The complete file
Here is the complete file:
# SPFx Build Pipeline
# Author: seryoga@spfx-app.dev
# Version: 1.0
# Steps:
# Step 1: checkout settings
# Step 2: Set git user
# Step 3: Check commit message and determine version bump option
# Step 4: Git checkout
# Step 5: Use Node Version 10.x
# Step 6: Execute "npm Install"-Command
# Step 7: Execute "gulp clean"-Command
# Step 8: Execute "gulp bump-version"-Command
# Step 9: (Disabled): Execute "gulp build --ship"-Command (not mandatory, you can disable this command when you want by adding to the task `enabled: false` after the `input`-section)
# Step 10: Execute "gulp bundle --ship"-Command
# Step 11: Execute "gulp package-solution --ship"-Command
# Step 12: Read version from package.json
# Step 13: Push the changes back to repository
# Step 14: Create Git Tag
# Step 15: Copy Files to: $(Build.ArtifactStagingDirectory)/drop
# Step 16: Publish Artifacts
trigger:
- main
pool:
name: Azure Pipelines
vmImage: 'ubuntu-latest'
steps:
- checkout: self
clean: true
persistCredentials: true
- script: |
git config --global user.email devops@spfx-app.dev & git config --global user.name "spfx-app.dev"
workingDirectory: $(System.DefaultWorkingDirectory)
- powershell: |
$commitMsg = "$(Build.SourceVersionMessage)"
Write-Host $commitMsg
Write-Host "Version bump is ENABLED"
$bumpVersionArgs = "--no-patch"
Write-Host "##vso[task.setvariable variable=BUMP_MAJOR_VERSION;]$false"
Write-Host "##vso[task.setvariable variable=BUMP_MINOR_VERSION;]$false"
Write-Host "##vso[task.setvariable variable=BUMP_PATCH_VERSION;]$false"
if($commitMsg -match "(major):.*") {
Write-Host "Bump Major Version"
Write-Host "##vso[task.setvariable variable=BUMP_MAJOR_VERSION;]$true"
$bumpVersionArgs = "--major"
}
elseif($commitMsg -match "(minor):.*") {
Write-Host "Bump Minor Version"
Write-Host "##vso[task.setvariable variable=BUMP_MINOR_VERSION;]$true"
$bumpVersionArgs = "--minor"
}
else {
Write-Host "Bump Patch Version"
Write-Host "##vso[task.setvariable variable=BUMP_PATCH_VERSION;]$true"
$bumpVersionArgs = "--patch"
}
Write-Host "##vso[task.setvariable variable=BUMP_VERSION_ARGS;]$bumpVersionArgs"
displayName: Get the commit message
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
- script: |
git checkout $(Build.SourceBranch)
displayName: Checkout Branch
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
- task: NodeTool@0
displayName: 'Use Node 10.x'
inputs:
versionSpec: 10.x
checkLatest: true
enabled: true
- task: Npm@1
displayName: 'npm install'
inputs:
workingDir: ' $(Build.Repository.LocalPath)'
verbose: true
enabled: true
- task: gulp@0
displayName: 'gulp clean'
inputs:
targets: clean
enabled: true
- task: gulp@0
displayName: 'gulp build --ship'
inputs:
targets: build
arguments: '--ship'
enabled: false
- task: gulp@0
displayName: 'gulp bump-version'
inputs:
targets: 'bump-version'
arguments: $(BUMP_VERSION_ARGS)
continueOnError: true
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
enabled: true
- task: gulp@0
displayName: 'gulp bundle --ship --no-patch'
inputs:
targets: bundle
arguments: '--ship --no-patch'
continueOnError: true
enabled: true
- task: gulp@0
displayName: 'gulp package-solution --ship'
inputs:
targets: 'package-solution'
arguments: '--ship'
enabled: true
- powershell: |
$newVersion = (Get-content ./package.json| out-string | ConvertFrom-Json).version
Write-Host "##vso[task.setvariable variable=NEW_VERSION;]$newVersion"
Write-Host $newVersion
displayName: Get Version from package.json and set in variable
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
- script: |
echo $(NEW_VERSION)
git commit -a -m "[skip ci] Version updated to $(NEW_VERSION)"
git push
displayName: Push changes to master
workingDirectory: $(System.DefaultWorkingDirectory)
condition: eq(variables['CI_BUMP_VERSION'], 'TRUE')
- script: |
git tag $(NEW_VERSION) HEAD
git push --tags
displayName: Create Tag for last commit version
workingDirectory: $(System.DefaultWorkingDirectory)
condition: and(eq(variables['CI_BUMP_VERSION'], 'TRUE'), eq(variables['CI_CREATE_GIT_TAG'], 'TRUE'))
- task: CopyFiles@2
displayName: 'Copy Files to: $(Build.ArtifactStagingDirectory)/drop'
inputs:
Contents: '**/*.sppkg'
TargetFolder: '$(Build.ArtifactStagingDirectory)/drop'
enabled: true
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'
enabled: true
Creating the variables
Before you save the pipeline, you must create variables. To do this, click on the "Variables" button at the top right of the editor and then on "+". Enter the variable name CI_BUMP_VERSION
and the value TRUE
. If you want to change these values when running manually, you have to select "Let users override this value when running this pipeline". Save the variable and do the same with the variable CI_CREATE_GIT_TAG
. Save the settings and then save the pipeline. Now it is ready to run.
Result
When the pipeline has gone through, it now looks like this:
And the tags were created:
Again, as a hint: Through the commit message you can define whether major, minor or patch version should be counted up.
Happy Coding ;-)