Skip to main content

Command Palette

Search for a command to run...

Package SPFx solution with one command and automatically increase the version

Updated
7 min read
Package SPFx solution with one command and automatically increase the version
$

I am Sergej and I am a Software Architect from Germany (AURUM GmbH). I have been developing on Microsoft technologies for more than 14 years, especially in SharePoint / Microsoft 365. With this blog, I want to share my knowledge with you and also improve my English skills. The posts are not only about SPFx (SharePoint Framework) but also about tips & tricks around the M365 world & developments of all kinds. The posts are about TypeScript, C#, Node.js, Vue.js, Visual Studio/ VS Code, Quasar, PowerShell, and much more. I hope you will find some interesting posts. I would also be happy if you follow me. Greetings from Germany Sergej / $€®¥09@

In SPFx solutions, the version number is stored in the package-solution.json file. The versioning follows the scheme used in assemblies, for example. For example, 1.0.0.0 (Major.Minor.Build.Revision).

Most people (and I have to admit that I am one of them) almost never incremented this number. At least the packages did not have to be "updated" manually and received all changes immediately. You also have to remember to update the version number. It is, of course, clear that this is not the purpose of versioning. But recently, Microsoft has published an important update, which obliges to update the version number. Because if you do not updates the version, the solution changes will not be visible. So you have two options:

  1. Uninstall solution, delete solution from the Recycle Bin, delete solution from the 2nd Stage Recycle Bin and then reinstall.

  2. Update the version number and then perform an "update" for the app.

I think it is clear which is faster/easier. But the problem is that you must not forget to update the version. I came across the "automatic version increment" on a blog a few years ago, which uses a gulp task. But - as described above - I didn't see the point in incrementing the version because the other thing is easier. Upload solution - done. No update, no waiting. It is simple and fast. With the Microsoft Update, such a task makes sense, of course. So I went looking for this script again and came across another - but still useful - blog article by Tom Daly. I found his code quite good and wanted to adapt it to my needs.

For my SPFx solutions that I publish on GitHub, I include the version number in the web parts. This has become established for many WebParts. For this purpose, I usually use the PnP Property Pane Controls and the control PropertyPaneWebPartInformation. To display the version number, you can access the Web Part context object this.context.manifest.version. The problem is that this is not the version number from package-solutions.json, but the one from package.json. This is needed for npm. But npm, like many others, uses the Semantic Versioning. This version has 3 digits. So 1.0.0 (Major.Minor.Patch).

My goal was to automatically update both, the package-solution.json and the package.json and to keep them "in sync". The revision number of the package-solution.json is completely ignored. The version number should always be incremented automatically when the gulp bundle command is executed with the parameter --ship. Alternatively, you can just update the version with the command gulp bump-version. You can specify which part of the versioning should be updated with the additional parameters --major, --minor or --patch. Whereas --patch is the default. If you don't want to update the version in the gulp bundle --ship command, you can just type gulp bundle --ship --no-patch and then nothing will be done with the version.

To use my script, you need to add the following lines to the top of gulpfile.js (if not already there):

const gulp = require('gulp'); 
const gutil = require('gulp-util'); 
const fs = require('fs');

Then replace the last line build.initialize(require('gulp')); with build.initialize(gulp);. And before this line, insert the following code:

var getJson = function (file) {
  return JSON.parse(fs.readFileSync(file, 'utf8'));
};

let bumpVersionSubTask = build.subTask('bump-version-subtask', function(gulp, buildOptions, done) {

  const currentCommand = buildOptions.args._[0];

  const skipFunc = gulp.src('./config/package-solution.json').pipe(gutil.noop());

  if(typeof currentCommand != "string") {
    gutil.log("The current command is undefined, skip version bump");
    return skipFunc;
  }

  const commandName = currentCommand.toLocaleLowerCase();

  if(commandName != "bundle" && commandName != "bump-version") {
    gutil.log("The current command is not 'bundle' or 'bump-version', skip version bump");
    return skipFunc;
  }

  const bumpVersion = commandName == "bump-version" || buildOptions.args["ship"] === true;

  if(!bumpVersion) {
      gutil.log("The current command is not 'bump-version' or the --ship argument was not specified, skip version bump");
      return skipFunc;
  }

  const a = buildOptions.args;

  const skipMajorVersion = typeof a["major"] == "undefined" || a["major"] === false;
  const skipMinorVersion = !skipMajorVersion || typeof a["minor"] == "undefined" || a["minor"] === false;
  const skipPatchVersion = !skipMajorVersion || !skipMinorVersion || a["patch"] === false;

  if(skipMajorVersion && skipMinorVersion && skipPatchVersion) {
    gutil.log("skip version bump, because all specified arguments (major, minor, patch) are set to 'false'")
    return skipFunc;
  }

  const pkgSolutionJson = getJson('./config/package-solution.json');
  const currentVersionNumber = String(pkgSolutionJson.solution.version);
  let nextVersionNumber = currentVersionNumber.slice();
  let nextVersionSplitted = nextVersionNumber.split('.');
  gutil.log('Current version: ' + currentVersionNumber);

  if(!skipMajorVersion) {
    nextVersionSplitted[0] = parseInt(nextVersionSplitted[0]) + 1;
    nextVersionSplitted[1] = 0;
    nextVersionSplitted[2] = 0;
    nextVersionSplitted[3] = 0;
  }

  if(!skipMinorVersion) {
    nextVersionSplitted[1] = parseInt(nextVersionSplitted[1]) + 1;
    nextVersionSplitted[2] = 0;
    nextVersionSplitted[3] = 0;
  }

  if(!skipPatchVersion) {
    nextVersionSplitted[2] = parseInt(nextVersionSplitted[2]) + 1;
    nextVersionSplitted[3] = 0;
  }

  nextVersionNumber = nextVersionSplitted.join(".");

  gutil.log('New version: ', nextVersionNumber);

  pkgSolutionJson.solution.version = nextVersionNumber;
  fs.writeFile('./config/package-solution.json', JSON.stringify(pkgSolutionJson, null, 4), () => {});

  const packageJson = getJson('./package.json');
  packageJson.version = nextVersionNumber.split('.').splice(0, 3).join(".");
  fs.writeFile('./package.json', JSON.stringify(packageJson, null, 4), () => {});

  return gulp.src('./config/package-solution.json')
  .pipe(gulp.dest('./config'));
});

let bumpVersionTask = build.task('bump-version', bumpVersionSubTask);
build.rig.addPreBuildTask(bumpVersionTask);

The complete "gulpfile.js" looks like this:

'use strict';

const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
const gutil = require('gulp-util');
const fs = require('fs');

build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);

var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
  var result = getTasks.call(build.rig);

  result.set('serve', result.get('serve-deprecated'));

  return result;
};

var getJson = function (file) {
  return JSON.parse(fs.readFileSync(file, 'utf8'));
};

let bumpVersionSubTask = build.subTask('bump-version-subtask', function(gulp, buildOptions, done) {

  const currentCommand = buildOptions.args._[0];

  const skipFunc = gulp.src('./config/package-solution.json').pipe(gutil.noop());

  if(typeof currentCommand != "string") {
    gutil.log("The current command is undefined, skip version bump");
    return skipFunc;
  }

  const commandName = currentCommand.toLocaleLowerCase();

  if(commandName != "bundle" && commandName != "bump-version") {
    gutil.log("The current command is not 'bundle' or 'bump-version', skip version bump");
    return skipFunc;
  }

  const bumpVersion = commandName == "bump-version" || buildOptions.args["ship"] === true;

  if(!bumpVersion) {
      gutil.log("The current command is not 'bump-version' or the --ship argument was not specified, skip version bump");
      return skipFunc;
  }

  const a = buildOptions.args;

  const skipMajorVersion = typeof a["major"] == "undefined" || a["major"] === false;
  const skipMinorVersion = !skipMajorVersion || typeof a["minor"] == "undefined" || a["minor"] === false;
  const skipPatchVersion = !skipMajorVersion || !skipMinorVersion || a["patch"] === false;

  if(skipMajorVersion && skipMinorVersion && skipPatchVersion) {
    gutil.log("skip version bump, because all specified arguments (major, minor, patch) are set to 'false'")
    return skipFunc;
  }

  const pkgSolutionJson = getJson('./config/package-solution.json');
  const currentVersionNumber = String(pkgSolutionJson.solution.version);
  let nextVersionNumber = currentVersionNumber.slice();
  let nextVersionSplitted = nextVersionNumber.split('.');
  gutil.log('Current version: ' + currentVersionNumber);

 if(!skipMajorVersion) {
    nextVersionSplitted[0] = parseInt(nextVersionSplitted[0]) + 1;
    nextVersionSplitted[1] = 0;
    nextVersionSplitted[2] = 0;
    nextVersionSplitted[3] = 0;
  }

  if(!skipMinorVersion) {
    nextVersionSplitted[1] = parseInt(nextVersionSplitted[1]) + 1;
    nextVersionSplitted[2] = 0;
    nextVersionSplitted[3] = 0;
  }

  if(!skipPatchVersion) {
    nextVersionSplitted[2] = parseInt(nextVersionSplitted[2]) + 1;
    nextVersionSplitted[3] = 0;
  }

  nextVersionNumber = nextVersionSplitted.join(".");

  gutil.log('New version: ', nextVersionNumber);

  pkgSolutionJson.solution.version = nextVersionNumber;
  fs.writeFile('./config/package-solution.json', JSON.stringify(pkgSolutionJson, null, 4), () => {});

  const packageJson = getJson('./package.json');
  packageJson.version = nextVersionNumber.split('.').splice(0, 3).join(".");
  fs.writeFile('./package.json', JSON.stringify(packageJson, null, 4), () => {});

  return gulp.src('./config/package-solution.json')
  .pipe(gulp.dest('./config'));
});

let bumpVersionTask = build.task('bump-version', bumpVersionSubTask);
build.rig.addPreBuildTask(bumpVersionTask);

build.initialize(gulp);

Now you can use the task gulp bundle --ship or gulp bump-version to automatically bump up the version.

One command for complete release including version update

To publish the package, you normally have to execute the following commands:

gulp clean

gulp build

gulp bundle --ship

gulp package-solution --ship

Of course, you can also use the shorthand notation gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship. But this also takes a long time. That's why I show you here how you can do everything at once with one command. Including version updates. I have thought of the command publish for this. You can simply use the npm scripts for this. Open the package.json file and enter the following under the scripts property:

"publish": "gulp clean && gulp build && gulp bundle --ship",
"postpublish": "gulp package-solution --ship"

package.json file example

Now you can just run npm run publish in the terminal and you have everything in one command. Even the version is incremented because of our previously described gulp task.

By the way, if you don't want the version to be incremented, just run the command like this npm run publish -- --no-patch. If the major version is to be updated, then just use npm run publish -- --major. For minor then npm run publish -- --minor.

I hope it has helped you and that you - like me - have saved some time.

Happy coding ;-)

3.2K views
J

Very useful. Deployed it instantly

1
$

Thank you Jonathan Adcox Just for me out of interest. Does your comment only refer to the Gulp commands (from the article) or also to the CLI (which I mentioned in the comments)? BTW: If you're interested, I also have an article on how you can integrate the same logic into an Azure DevOps pipeline to increment the version, create the package and create another Git tag with the same version number

1
J

$€®¥09@ to the Gulp command in the article. However there were a few bugs. When I build and clean it will not run as intended and only when I use the gulp command directly does it build properly and it builds with Major version only. Did you address this in the comments?

$

Jonathan Adcox Hmmm, where exactly is the error? I've never had this before. I use npm run publish and this command does a gulp clean and then a gulp build, gulp bundle --ship, gulp bump-version and then gulp package-solution --ship one after the other.

Normally only the patch version is updated, unless you specify something else.

By the way: If you use the command npm run publish and there was a warning/error in the console output, then the commands are not executed, with the output exit with code 1 (or something like that). This is due to the && command combination. But you can prevent this by disabling the warnings.

PS: As mentioned in the other comments, I can recommend using my CLI, which does exactly the same settings as described in this article, and does so with a command line and also adds the "disable warnings" command. And some more. Here is the link to the CLI article (with Video): https://spfx-app.dev/my-cli-for-spfx-development

1
J

$€®¥09@ oh I already disabled those errors due to an unused variable that is only accessed in SharePoint however in the build it is never used. As such I had to disable the warning to build the solution. I likely fat fingered something. I will go back and see what I transposed wrong

1
$

Hello Thomas Leclaire, hello Sujit Daswant,

now you can use my newly released npm package @spfxappdev/cli. This package is a command-line utility that modifies your SharePoint Framework solution so that you can use the versioning logic described above without additional modifications. You can use "alias" paths instead of relative paths. Also, an automatic "version increment" task (gulp) is included and you can build, version, bundle and package the solution with just one npm command.

A blog post will follow in the next few days.

S

Hi Sergej Schwabauer, I am really appreciate for this blog, this is helped me lot for Auto increment the the version number.

But I am facing issue with the above, when I am trying to run the command for npm run publish -- --no-patch,npm run publish -- --major,npm run publish -- --minor version always the minor version get updated even for no patch also.

Please let me know how to fix this.

$

Hi Sujit Daswant Thank you for your comment. I don't know what the problem could be. Could you tell what the output window is showing you? Any comments from my command? Is minor version incremented in both json files (package.json and package-solution.json)? Have you tried the gulp command instead of npm run? I mean gulp bump-version or gulp bump-version --major?

1
S

$€®¥09@ , Thanks for quick response. As per mentioned in comment I have tried to run the command and its working fine. version is getting updated as expected.

image.png

I think the issue is with this command, npm run publish -- --no-patch npm run publish -- --major npm run publish -- --minor

I have followed the steps as per mentioned in this blog.

$

Sujit Daswant Okay, I understand. And now I know what you mean and what the problem is. The npm run command with the additional arguments is only added to the "last" command (in our case ==> gulp package solution command). But we need this argument in the gulp bundle command. Thanks for your hint. To fix it, you need to create a post-script and modify the "publish" script:

"publish": "gulp clean && gulp build && gulp bundle --ship",
"postpublish": "gulp package-solution --ship"

Thanks again and I have already changed it in the post above.

1
S

$€®¥09@ ,

I am really appreciate your solution. The solution which you have provided is worked for me and also seen the updated changed in the blog.

image.png

Now the Auto Version are updating as per expectation.

Keep posting your valuable experience with us.

Happy Coding ;-)

1
T

How would you use this in an Azure Devops Deployment pipeline where you would have to save the increased version back to the repo?

$

Thomas Leclaire Thank you for your comment. Please have a look at this newly created post. I hope it helps you: https://spfx-app.dev/spfx-azure-devops-pipeline-increment-version-push-to-repository-and-publish-package

2

More from this blog

S

SharePoint SPFx Development by $€®¥09@

30 posts