SPFx Azure DevOps Pipeline: Increment version, push to repository and publish package

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

Play this article

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.

Userpermission settings in Azure DevOps

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.

Add new Variables

Result

When the pipeline has gone through, it now looks like this:

Commit message

And the tags were created:

Git tags

Again, as a hint: Through the commit message you can define whether major, minor or patch version should be counted up.

Happy Coding ;-)

Did you find this article valuable?

Support $€®¥09@ by becoming a sponsor. Any amount is appreciated!