76

I want to have a timestamp or build number somewhere on my Angular2 App so I can tell if a user is using an old cached version or not.

How to do this with AngularCLI in Angular2 at AOT compile/build time?

slartidan
  • 20,403
  • 15
  • 83
  • 131
Rodney
  • 5,417
  • 7
  • 54
  • 98

12 Answers12

80
  1. Install plugin npm install replace-in-file --save-dev
  2. Add to prod environment src/environments/environment.prod.ts new property

    export const environment = {
        production: true,
        version: '{BUILD_VERSION}'
    }
    
  3. Add build file replace.build.js to root of your folder

    var replace = require('replace-in-file');
    var buildVersion = process.argv[2];
    const options = {
        files: 'src/environments/environment.prod.ts',
        from: /{BUILD_VERSION}/g,
        to: buildVersion,
        allowEmptyPaths: false,
    };
    
    try {
        let changedFiles = replace.sync(options);
        console.log('Build version set: ' + buildVersion);
    }
    catch (error) {
        console.error('Error occurred:', error);
    }
    
  4. add scripts to package.json

    "updateBuild": "node ./replace.build.js"
    
  5. Use environment.version in your app

  6. Before build call npm run updateBuild -- 1.0.1

PS. You must always remember that {BUILD_VERSION} is never committed.

PS. I wrote a bit better solution in my blog

PS.3 as @julien-100000 mentioned you should not commit environment.prod.ts with updated version. Version update must happen only in build process. And should never be committed.

Milo
  • 3,365
  • 9
  • 30
  • 44
Vova Bilyachat
  • 18,765
  • 4
  • 55
  • 80
  • Thanks, that is what I was looking for. A few nice enhancements would be to generate a timestamp or autoincrement the build number (instead of having to input it). – Rodney Jan 21 '17 at 10:42
  • 1
    @Rodney I actually just thought about that. Will do it and write in my blog :) – Vova Bilyachat Jan 21 '17 at 10:42
  • @VolodymyrBilyachat getting build version in environment.prod.ts using double quote cause problem – rpmansion Aug 22 '17 at 07:21
  • @rpmansion can you at least describe a problem? i don't see any problem you can have – Vova Bilyachat Aug 23 '17 at 02:17
  • 1
    `replace-in-file` module has updated their options. Should use `const options = { files: 'src/environments/environment.prod.ts', from: /{BUILD_VERSION}/g, to: buildVersion };` – Blexy Oct 06 '17 at 00:42
  • 1
    Actually the app version is anyway stored in package.json. Is there any way to use that? – aholbreich Jan 15 '18 at 09:46
  • That's a good point, but I feel like the script should also update the package.json version. There's likely a way of having the replace library look at multiple files besides just a directory wildcard. – Matt Eland Jan 31 '18 at 03:44
  • I don't understand why you want to replace in file!! you can easily require `package.json` – Vahid Jun 09 '18 at 12:24
  • @VolodymyrBilyachat Yes I've used it in multiple projects. You can see my answer: https://stackoverflow.com/a/50774271/1889607 – Vahid Jun 09 '18 at 12:36
  • @aholbreich actually you can modify build script to with require(package.json).version then it will get that version from there and replace – Vova Bilyachat Jun 09 '18 at 12:58
  • This solution here only changes the version for one build after insert `version: '{BUILD_VERSION}'` in the _environment.prod.ts_, because he will replace the `{BUILD_VERSION}` with the actual version name during the build and so he is unable to find this keyword it for the next build. **So please look in the blog, because the solution there works for infinitely builds.** – julienduchow Oct 10 '18 at 20:56
  • @julien-100000 yes it should be done in build only otherwise it does not make sense if you updated on commit – Vova Bilyachat Oct 10 '18 at 21:56
  • hmm I did it exatly like described here, which is to integrate it in the build or not?, and it still only updated it once. – julienduchow Oct 12 '18 at 14:49
  • 1
    One suggestion for those who do not want to care of not committing environment.prod.ts` by accident you can change regex expression. Here is an example and I added a build timestamp as well. ```let const buildVersionOptions = { files: 'src/environments/environment.prod.ts', from: /version:.*/gm, to: `version: '${buildVersion}',`, allowEmptyPaths: false, }; const timeStampOptions = { files: 'src/environments/environment.prod.ts', from: /buildTimestamp:.*/gm, to: `buildTimestamp: '${new Date().toISOString()}',`, allowEmptyPaths: false, }; ``` – Vlad May 22 '19 at 13:26
25

Add this step to your build (ie Jenkins-Job):

echo export const version = { number: '%SVN_REVISION%' } > src\version.ts

You can access the number like this:

import { version } from "../version";

export class AppComponent {
    constructor() {
        console.log("MyApp version " + version.number);
    }
}

This solution is + lightweight, + easy to read, + robust.

Anulal S
  • 6,534
  • 5
  • 26
  • 33
slartidan
  • 20,403
  • 15
  • 83
  • 131
  • 1
    Which jenkins job? There are different CI solutions with no jenkings as well. – Rantiev Apr 27 '20 at 09:42
  • @Rantiev You have a point with that. However this simple approach should work with almost any build environment (maybe with slightly modified shell script). I updated my post. – slartidan Apr 28 '20 at 04:56
  • 1
    This is the simplest and best solution. I used following command in my case `echo "export const RunTimeFile = { buildtime:new Date('$(date -Is)') };" | tee src/environments/runtime-file.ts` – mahindar Sep 25 '20 at 04:25
22

There's no need to replace-in-file.

Simple Solution: Using Angular Environments

Just inside your desired environment.*.ts file (For more information about environments read angular-2-and-environment-variables) require package.json like so:

export const environment = {
    version: require('../package.json').version
};

Then inside your app import environment:

import { environment } from '../environments/environment';

And you have environment.version. If you get cannot find name 'require' error, Read this answer

More info

Note: As @VolodymyrBilyachat mentioned in comments, this will include your package.json file in the final bundle file.

Vahid
  • 6,639
  • 5
  • 37
  • 61
  • Awesome but here is one question which bother me. How tree shaking is working with require ? – Vova Bilyachat Jun 09 '18 at 12:40
  • @VolodymyrBilyachat I'm not sure if I understand this well, but tree shaking is about removing unused codes. I found this link which may be related: [Angular Tree Shaking](https://blog.rangle.io/optimize-your-angular2-application-with-tree-shaking/). Read the title "Why Typescript 2 is Needed" – Vahid Jun 09 '18 at 12:50
  • Ok let me ask you other question. Have you looked in minified scripts after you build your application ? – Vova Bilyachat Jun 09 '18 at 12:51
  • @VolodymyrBilyachat No. What should I look for? – Vahid Jun 09 '18 at 12:54
  • 7
    Reason I ask about how require works with tree shaking is because basically its injecting everything to your bundle. So if you do require package.json you should understand that WHOLE content of this file will be bundled into your min scripts. If you fine with that then its fine. And yes your link is totally unrelated to my question – Vova Bilyachat Jun 09 '18 at 12:56
  • In angular 6 project, sometimes it work fine, but usually it says "ERROR in src/environments/environment.ts(6,25): error TS2307: Cannot find module '../../package.json'.". Do you have any idea why this is happening? Thanks. I am using import { version } from "../../package.json"; – Vojtech Ruzicka Jun 25 '18 at 15:44
9

I solved this by appending a comment at the end of index.html with the last commit hash. For example:

ng build --prod

git rev-parse HEAD | awk '{print "<!-- Last commit hash: "$1" -->"}' >> dist/index.html

You can then do a "View Source" in the browser, look at the bottom of the HTML, and see the deployed version of your app.

This of course assumes that you use git as the versioning system. You could easily change git rev-parse HEAD with any other command that outputs a unique version.

tedw
  • 451
  • 5
  • 8
  • My understanding of 'build' and 'build number' are that they are not equivalent to a commit - there could be many builds before a commit. – Tom Mar 10 '19 at 17:54
  • I used your approach and doing: date/T >> dist\index.html (Gives me info when the last build was created) Working Great for me! – David Jun 13 '19 at 15:53
  • Thanks for this trick. I was about to ask a question here then stumbled upon your answer. – Kihats Mar 18 '20 at 16:34
  • This was very helpful. I wanted to add a build date as well, so I used the following: printf '\n' "$(git rev-parse HEAD)" "$(TZ='America/New_York' date)" – Steig Hallquist Jan 07 '21 at 15:58
9

May be a bit late for the discussion, but hopefully I can help someone else looking at the same problem.

I have written a small npm package called angular-build-info which sums up some information about the current build such as a build timestamp, the git user which built the app, a shortened commit hash and the apps version from your projects package.json file.

Implementing the package is also pretty easy, it creates a build.ts file under src/build.ts which you can then import in your Angular app and display the information anywhere.

An example of implementing it could look as follows: (app.component.ts)

import { Component } from "@angular/core";
import { buildInfo } from "../build";
import { environment } from "../environments/environment";

@Component({
    selector: "app-root",
    templateUrl: "./app.component.html",
    styleUrls: ["./app.component.css"]
})
export class AppComponent {
    constructor() {
        console.log(
            `\n%cBuild Info:\n\n%c ❯ Environment: %c${
                environment.production ? "production " : "development "
            }\n%c ❯ Build Version: ${buildInfo.version}\n ❯ Build Timestamp: ${
                buildInfo.timestamp
            }\n ❯ Built by: ${buildInfo.user}\n ❯ Commit: ${buildInfo.hash}\n`,
            "font-size: 14px; color: #7c7c7b;",
            "font-size: 12px; color: #7c7c7b",
            environment.production
                ? "font-size: 12px; color: #95c230;"
                : "font-size: 12px; color: #e26565;",
            "font-size: 12px; color: #7c7c7b"
        );
    }
}

which would output: link to console screenshot

I hope this will help someone! :)

Juri Adams
  • 91
  • 1
  • 3
  • 2
    nice package but I don't want to add another dependency to my project for this simple functionality. – canbax Oct 08 '20 at 07:33
7
  1. Create simple js file createBuildDate.js

    for example:

    const { writeFileSync } = require('fs')
    const { join } = require('path')
    
    const TIME_STAMP_PATH = join(__dirname, 'buildDate.json');
    
    const createBuildDate = {
        year: new Date()
    }
    
    writeFileSync(TIME_STAMP_PATH, JSON.stringify(createBuildDate, null, 2));  
    
  2. Edit script in package.json

    "build": "node src/environments/createBuildDate.js && ng build"
    

    or you can use prebuild

  3. Edit tsconfig.json

    "resolveJsonModule": true,
    
  4. Import json in component and use

Josef
  • 2,869
  • 2
  • 22
  • 23
klivladimir
  • 216
  • 5
  • 5
6

I looked at other solutions, but I have not been convinced by either using yet another package or generating a JSON (because the JSON is not exactly in the app). Also, I did not want to bother with uncommitted changes like in the accepted answer of this thread.

I end-up by making a tiny bash script that replaces automatically a string located somewhere in my App with the version got from GIT + the DateTime of the build.

Now when I build my app, I just need to execute the following script:

latesttag=$(git describe --tags)
now=$(date +'%a %y-%m-%d %H:%M')

sed -i '' "s/build_info/$latesttag/g" ./src/app/pages/home/user-preference/user-preference.component.html
sed -i '' "s/build_time/$now/g" ./src/app/pages/home/user-preference/user-preference.component.html

ng build --prod;

sed -i '' "s/$latesttag/build_info/g" ./src/app/pages/home/user-preference/user-preference.component.html
sed -i '' "s/$now/build_time/g" ./src/app/pages/home/user-preference/user-preference.component.html

And in my file user-preference.component.html

<footer class="footer mt-auto">
    <div class="container text-center">
        Build: build_info - build_time
    </div>
</footer>
Pierre
  • 1,044
  • 15
  • 27
  • 1
    This is such a great solution, no dependencies, easy to integrate into build steps. – Adam Thompson Jun 20 '21 at 04:16
  • 1
    Thanks! Another advantage of this approach is that it can be integrated with other deployment actions. In my case, I need to generate a zip folder for the project implementation team, thus I added: zip -rq ... ... ; scp .... :) – Pierre Jun 20 '21 at 13:43
3

Just for reference, here is a platform-independent version of slartidan's answer. The timestamp is updated during build using a node.js script (bin/update-timestamp.js).

bin/update-timestamp.js

#!/usr/bin/env node
'use strict';

const fs = require('fs');
const stamp = new Date().toISOString();
fs.writeFileSync('./src/app/timestamp/Timestamp.ts', `export class Timestamp { public static readonly stamp = '${stamp}'; }`);

package.json

{
  "scripts": {
    "build": "node ./bin/update-timestamp.js && ng build --prod",
  },
}

app.component.ts

import {Timestamp} from './timestamp/Timestamp';

export class AppComponent {

  constructor() {
    console.log("timestamp", Timestamp.stamp);
  }
  
}
Dan
  • 2,157
  • 21
  • 15
0

If you want to log/display not only the version number but also information from git (like the hash, tag, etc) you can consider using a custom schematic for the angular-cli that I created. Adding it to your Angular 8+ Application is as simple as executing ng add @w11k/git-info

For further Documentation and insights you can look at the documentation available at https://github.com/w11k/angular-git-info

Kai Henzler
  • 23
  • 1
  • 1
  • 3
0

Executing bash/shell commands inside npm scripts might be hard if you want to pass arguments, expand variables/functions etc.

So maybe the simplest solution would be to leverage javascript's string interpolation which is excellent, and the simplest file append operator >> of unix based systems :

package.json

  "build": "ng build --prod && node mypostbuildscript.js >> dist/index.html"

mypostbuildscript.js

console.log(`<!-- build timestamp: ${Date.now()} -->`);
ktsangop
  • 1,013
  • 2
  • 16
  • 29
-4

Perhaps this is a good solution for someone.. https://medium.com/@amcdnl/version-stamping-your-app-with-the-angular-cli-d563284bb94d

What he describes is how to use your git data and have the last commit hash as build number.

By adding a postinstall step in your package.json a file will be generated when running the install script.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Mackelito
  • 4,213
  • 5
  • 39
  • 78
-6

I think for your case: ng build or ng serve add in environment.ts:

export class environment {
  production: false,
  buildTimestamp: new Date()
}

Then in your component:

import { environment } from 'src/environments/environment'; // or path to your environment.ts file

...

const buildTimestamp = environment.buildTimestamp;

This is what I was looking for.

  • 4
    This does not keep the timestamp of the build. This will update every time the page has loaded. – Chad K Jul 26 '19 at 14:40