In most applications, that run on your computer, you find in a menu some information about the version of the application.
About dialog from Notepad++
This information is useful for the user if he wants to know if he runs the newest version. They are also useful for the developer when users send the version number together with error reports.
Exposing build information could also be useful for web applications. You, as the developer, could verify with a version number if the right package is installed on a production server. Especially useful if you are not in charge of installing applications in the production environment.
Another use case for exposing build information in web applications is to verify if a Service Worker works correctly. By checking the version number, you can verify that the Service Worker loaded and cached the new version after an application update.
In the following tutorial, we are going to see how to expose build information from a Spring Boot and Angular application.
Spring Boot ¶
Spring Boot has built-in support for exposing build information; we only have to enable it. With Actuator on the classpath almost everything is autoconfigured, but first, we look at an example without Actuator.
Without Actuator ¶
Spring Boot exposes build information like version number and build timestamp through the bean BuildProperties
.
However, before you can access this information you need to configure the build step and add the build-info
goal to the spring-boot-maven-plugin
, if you use Maven.
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
For Gradle, check out the documentation:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-build-info
When you compile your application, you will find a new file
in the target folder: target/classes/META-INF/build-info.properties
This file contains information from the pom.xml
, like version, group and artifact id.
Spring Boot automatically reads this file during start-up and instantiates the BuildProperties
singleton,
that you can inject into any Spring-managed class.
@Autowired
private BuildProperties buildProperties;
@GetMapping("/build-info")
public BuildProperties buildInfo() {
return this.buildProperties;
}
With a simple GET endpoint, you can expose the information. Only expose the information that you need. If you only need a version number, create a more slimmed-down GET endpoint.
@GetMapping("/version")
public String version() {
return this.buildProperties.getVersion();
}
Similar to build information, Spring Boot is also able to expose Git information, like branch name and commit id, if your project is under Git control. Similar to the build information, you need to configure your build system first.
In a Maven project, you need to add this plugin.
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
</plugin>
See the official documentation for setting up a Gradle project.
This plugin creates the file target/classes/git.properties
during build time, and Spring Boot reads it and creates the singleton GitProperties
.
public GitProperties gitInfo() {
return this.gitProperties;
}
@Autowired
private Environment environment;
@GetMapping("/profile-info")
Another useful information in a Spring application is the active profile.
For this, we don't need support from the build system; we can inject the Environment
singleton and access the active profile with getActiveProfiles()
.
@Autowired
private Environment environment;
@GetMapping("/profile-info")
public Map<String, Object> profileInfo() {
return Map.of("activeProfiles",
String.join(",", this.environment.getActiveProfiles()), "defaultProfiles",
String.join(",", this.environment.getDefaultProfiles()));
}
I would not recommend exposing all the information just because they are available. Only expose the information that you need. Also, make sure that you secure these endpoints in public-facing web applications. It makes very little sense to expose Git information to all users of your service. Maybe only expose the version number to all users and secure all other endpoints, so only the developers and operators of the service have access to.
With Actuator ¶
With Actuator on the classpath, we have to configure even less, because this library autoconfigures specific endpoints for us.
Make sure that you configure your build system according to the description in the previous section. Actuator depends on the same files and beans.
With the proper build setup, we only have to expose specific Actuator endpoints with a setting in
application.properties
.
management.endpoints.web.exposure.include=health,info,env
You can now access these endpoints with http://..../actuator/info
and http://..../actuator/env
Be careful with the /actuator/env
endpoint, which not only contains information about the active profile but also other information about the running Spring environment. If you are only interested in the
active profile, you are better off by writing your own GET endpoint that exposes just this information.
I recommend exposing these endpoints only if you need the information during runtime, and you should secure them because they are not useful for all users of your service.
If you want to expose a version number to all users, it is better to create a GET endpoint
that returns just the version number instead of all available build information and Git information
like the /actuator/info
does.
Angular ¶
In an Angular client application, we don't have built-in support for exposing build information, but it's not too complicated to expose this information.
An Angular app has, by default, when you created the app with the CLI, environment files in src/environments
.
This folder usually contains a file for development environment.ts
and one file for production environment.prod.ts
.
These files contain an environment
object similar to this example
export const environment = {
production: true,
serverURL: 'http://localhost:8080'
};
In your Angular application, you can import this file anywhere you want
import {environment} from '../environments/environment';
and access the information
const version = environment.version;
Make sure that you always import the environment
file without the suffix .prod
.
During a production build (ng build --configuration production
) the Angular CLI replaces environment.ts
with environment.prod.ts
, so it's essential you only reference the environment
file in your source code.
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
We can now leverage this environment system for exposing build-time information.
We create a new JavaScript file in the root of the project that reads the version number
from package.json
and writes this information into environment.prod.ts
.
I'm using here the replace-in-file
package that you need to install first.
npm install replace-in-file -D
const replaceInFile = require('replace-in-file');
const packageJson = require('./package.json');
const packageVersion = packageJson.version;
const simpleGit = require('simple-git')();
replaceInFile.sync({
files: './src/environments/environment.prod.ts',
from: [/version: '.+'/, /buildTimestamp: \d+/],
to: [`version: '${packageVersion}'`, `buildTimestamp: ${Math.floor(Date.now() / 1000)}`]
});
This script only works when the fields version
and buildTimestamp
exist in the environment
object. Add some dummy values before you run the script.
Then run the script from the shell with node. I called my script build-info-env.js
node ./build-info-env.js
Open environment.prod.ts
and check if the values are applied correctly.
export const environment = {
production: true,
serverURL: 'http://localhost:8080',
version: '0.0.1',
buildTimestamp: 1560334669
};
I also recommend adding these fields to the development environment file environment.ts
.
When you display the values in your application, you then always now if you work on the
development version or the production version.
export const environment = {
production: false,
serverURL: 'http://localhost:8080',
version: 'DEVELOPMENT',
buildTimestamp: null
};
Git information ¶
Next, we are going one step further and also expose Git information.
For this, I install the simpleGit
package. A very convenient library
if you need to run Git commands from Node.js applications.
npm install simpleGit -D
Then add the following code to the build-info-env.js
script
This code extracts the id and time of the last commit and writes it into
environment.prod.ts
.
simpleGit.log(['--pretty=format:%h,%H,%at', '-n', '1'], (err, log) => handleGitLog(log, err));
function handleGitLog(log, err) {
if (!err) {
const response = log.latest.hash;
const splitted = response.split(',');
const gitInfo = {
shortCommitId: splitted[0],
commitId: splitted[1],
commitTime: parseInt(splitted[2])
};
replaceInFile.sync({
files: './src/environments/environment.prod.ts',
from: [/shortCommitId: '.+'/, /commitId: '.+'/, /commitTime: \d+/],
to: [`shortCommitId: '${splitted[0]}'`, `commitId: '${splitted[1]}'`, `commitTime: ${splitted[2]}`]
});
} else {
console.log(err);
}
}
Make sure that the three fields shortCommitId
, commitId
and commitTime
exist in environment.prod.ts
with some dummy values before you start the script.
After you run the script with node ./build-info-env.js
, the environment
object looks like this.
import {Env} from './env';
export const environment: Env = {
production: true,
serverURL: 'http://localhost:8080',
version: '0.0.1',
buildTimestamp: 1700628854,
shortCommitId: '60302d1',
commitId: '60302d1d0670e687dbcac1bcb07a5debf3373583',
commitTime: 1699039865
};
You can then read these values in the TypeScript code
this.clientInfo = {
version: environment.version,
buildTimestamp: environment.buildTimestamp ? environment.buildTimestamp * 1000 : null,
shortCommitId: environment.shortCommitId,
commitId: environment.commitId,
commitTime: environment.commitTime ? environment.commitTime * 1000 : null
};
and display them to the user
<h3>Build Info</h3>
<p>Version: <strong>{{clientInfo.version}}</strong></p>
<p>Build Time: <strong>{{clientInfo.buildTimestamp | date:'medium'}}</strong></p>
<h3>Git Info</h3>
<p>Commit Id: <strong>{{clientInfo.commitId}}</strong></p>
<p>Short Commit Id: <strong>{{clientInfo.shortCommitId}}</strong></p>
<p>Commit Time: <strong>{{clientInfo.commitTime | date:'medium'}}</strong></p>
As mentioned in the Spring Boot section, I would not expose all this information just because it is available. Only extract the information you need and want to present it to the user. Especially if this a JavaScript running on the public Internet, you should be careful what information you expose.
Automated Release ¶
In this section, I show you an example of an automated build process for an Angular application.
The script build-info-env.js
we created in the previous step reads the version number from the package.json
file
and writes it into the environment file. You could manually increase the version number in package.json
every time
you release a version.
However, a more convenient way is to let npm do that. npm provides the version
command that
increases the version number. For example npm version patch
increments the third level of a version number (e.g. 1.0.1 -> 1.0.2).
With npm version minor
and npm version major
, you increment the minor and major part of the version number.
See the official documentation for more information.
With this command, we can now automate the version number increment step. In the scripts
section of package.json,
we add a new script.
"release-patch": "npm --no-git-tag-version version patch && npm run build-prod"
npm version
automatically creates a tag if you run this command in a clean Git repository. Because we are going to change
the environment file afterward, we don't want to create a Git tag here, so we disable this behavior with --no-git-tag-version
.
The release-patch
script calls the script build-prod
after incrementing the version number. But before we start
the production build, we want to run our build-info-env.js
script, which injects information into the environment file.
For this purpose, we add a prebuild-prod
script. You don't have to explicitly call the pre
and post
scripts because
npm automatically calls these scripts before and after a specific task.
"prebuild": "node ./build-info-env.js",
"postbuild": "node ./git-tag.js",
"release-patch": "npm --no-git-tag-version version patch && npm run build-prod"
In this example when you run npm run build
, npm first calls prebuild
, then build
and lastly postbuild
The script git-tag.js
is responsible for committing the changed file into Git and create a Git tag.
const packageJson = require('./package.json');
const packageVersion = packageJson.version;
const simpleGit = require('simple-git')();
simpleGit
.add('./*')
.commit(`release v${packageVersion}`)
.tag(['-a', `v${packageVersion}`, '-m', `release v${packageVersion}`]);
//.push('origin', 'master');
Now you have a completely automated build process, and whenever you want to release a patch version you issue the following command from the shell
npm run release-patch
For releasing a minor or major version, you could also add scripts into package.json
or type the command manually into the shell.
// Minor
npm --no-git-tag-version version minor && npm run build
// Major
npm --no-git-tag-version version major && npm run build
This concludes the tutorial about exposing build and Git information in Spring Boot and Angular applications.
You find the source code for all the examples presented in this blog post on GitHub:
https://github.com/ralscha/blog2019/tree/master/build-info