44

I need to display git revision on my angular2 application's about page. The project is based on angular-cli.

How can build be extended so git revision is put for example into environment.ts or other place accessible to application?

Vilmantas Baranauskas
  • 6,596
  • 3
  • 38
  • 50
  • 1
    I guess the *Angular-cli* lacks a needed pre-build scripting functionality. But you might use the plain *NPM scripting*, I guess. – Yuri Feb 11 '17 at 12:20

9 Answers9

57

As suggested by @Yuri, I was able to solve this by using npm scripting.

  1. Defined git.version.ts in the root of the angular-cli project:
import { Observable, combineLatest } from 'rxjs'

declare var require: any;
declare var process: any;

const fs = require('fs');
const exec = require('child_process').exec;

const revision = new Observable<string>(s => {
    exec('git rev-parse --short HEAD',
        (error: Error, stdout, stderr) => {
            if (error !== null) {
                console.log('git error: ' + error + stderr);
            }
            s.next(stdout.toString().trim());
            s.complete();
        });
});

const branch = new Observable<string>(s => {
    exec('git rev-parse --abbrev-ref HEAD',
        (error: Error, stdout, stderr) => {
            if (error !== null) {
                console.log('git error: ' + error + stderr);
            }
            s.next(stdout.toString().trim());
            s.complete();
        });
});

combineLatest(revision, branch)
    .subscribe(([revision, branch]) => {
        console.log(`version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'`);

        const content = '// this file is automatically generated by git.version.ts script\n' +
            `export const versions = {version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'};`;

        fs.writeFileSync(
            'src/environments/versions.ts',
            content,
            {encoding: 'utf8'}
        );
    });

  1. Added pre-build hook in package.json:
"scripts": {
    "ng": "ng",
    ...
    "start": "ng serve --proxy proxy-config.json",
    "prebuild.prod": "ts-node -O \"{\\\"module\\\":\\\"commonjs\\\"}\" git.version.ts",
    "build.prod": "ng build -prod",
    ...
}
  1. Use the generated src/environments/versions.ts in the application.

    UPDATE 10/2018: Here is the more-readable version of the script, rxjs-version-agnostic:

import { writeFileSync } from 'fs';
import { dedent } from 'tslint/lib/utils';
import { promisify } from 'util';
import * as child from 'child_process';
const exec = promisify(child.exec);

async function createVersionsFile(filename: string) {
  const revision = (await exec('git rev-parse --short HEAD')).stdout.toString().trim();
  const branch = (await exec('git rev-parse --abbrev-ref HEAD')).stdout.toString().trim();

  console.log(`version: '${process.env.npm_package_version}', revision: '${revision}', branch: '${branch}'`);

  const content = dedent`
      // this file is automatically generated by git.version.ts script
      export const versions = {
        version: '${process.env.npm_package_version}',
        revision: '${revision}',
        branch: '${branch}'
      };`;

  writeFileSync(filename, content, {encoding: 'utf8'});
}

createVersionsFile('src/environments/versions.ts');


Note, when using angular-cli v7.0.6, I also had to change script invocation in the package.json:

"scripts": {
    ...
    "prebuild.prod": "ts-node -O '{\"module\": \"commonjs\"}' git.version.ts",
    ...
}
Rohanrp
  • 3
  • 1
Vilmantas Baranauskas
  • 6,596
  • 3
  • 38
  • 50
  • 2
    ```ts-node git.version.ts``` command does not work and I don't know why I geting this: ```Did you mean one of these? rebuild prefix profile``` – George C. Mar 01 '18 at 10:43
  • 3
    For anyone coming to this using `angular 6` / `rxjs 6`: change the import to `import { Observable, combineLatest } from 'rxjs';`, and then replace `Observable.combineLatest` with just `combineLatest`. – Cameron Yick Aug 31 '18 at 20:50
  • This was a beautiful solution, but I also suggest adding the tag: `git describe --abbrev=0 --tags` – dessalines Sep 27 '18 at 02:44
  • 1
    How to generate the src/environments/versions.ts file? I call the `ng prebuild.prod` command but nothing happens. I wait for several minutes but no output. My versions: ```bash Angular CLI: 6.0.8 Node: 8.11.1 OS: win32 x64 Angular: 5.2.10 Package Version ----------------------------------------------------------- @angular/cli 6.0.8 @angular/pwa 0.6.5 @angular/service-worker 5.2.11 @ngtools/webpack 6.0.5 rxjs 5.5.10 ``` – Fitrah M Oct 08 '18 at 10:10
  • this is a very clean and nice way to acheive it!!! i thought sharing a code that i added to this solution that will print out more git information and jobname or BUILD_NUMBER if you are running it through jenkins – oded Oct 10 '18 at 15:26
  • here is the additional code: add 4 more observables lastCommitTime which is git log --format="%ai" -n1 HEAD, lastCommitMessage which is git log --format="%B" -n1 HEAD, lastCommitAuthor which is git log --format="%aN" -n1 HEAD, lastCommitNumber which is git rev-list --count HEAD, const commitSHA = process.env.GIT_COMMIT || branch; const buildInfo = process.env.BUILD_NUMBER || 'LOCAL ' + revision + ' - last commit number ' + lastCommitNumber + ', by ' + lastCommitAuthor + ' on ' + lastCommitTime; const jobName = process.env.JOB_NAME || 'local build' – oded Oct 10 '18 at 15:34
  • 1
    `-O '{\"module\": \"commonjs\"}'` in package.json didn't work for me (win10). I had to remove it and change the imports in git.version.ts to require statements. – MIWMIB Jun 07 '20 at 03:52
  • @MIWMIB yeah I had the same. At first it worked fine on my macos and build server (linux) but was failing on windoze with `'ts-node' is not recognized as an internal or external command, operable program or batch file` – theo Jan 08 '21 at 11:06
  • After updating Angular the error `Error: Debug Failure. False expression: Non-string value passed to "ts.resolveTypeReferenceDirective",...` appeared. Updating the package ts-node with command `npm update ts-node` solved this for me. see [False expression: Non-string value passed to `ts.resolveTypeReferenceDirective` in typescript](https://stackoverflow.com/questions/72488958/false-expression-non-string-value-passed-to-ts-resolvetypereferencedirective) – JIT Solution Jan 30 '23 at 14:51
24

The other answers were helpful, but I preferred a more simple, direct approach. Here's mine.

Run npm install git-describe --save-dev. Then add git-version.js to the root:

// This script runs operations *synchronously* which is normally not the best
// approach, but it keeps things simple, readable, and for now is good enough.

const { gitDescribeSync } = require('git-describe');
const { writeFileSync } = require('fs');

const gitInfo = gitDescribeSync();
const versionInfoJson = JSON.stringify(gitInfo, null, 2);

writeFileSync('git-version.json', versionInfoJson);

Optionally you can add /git-version.json to your .gitignore file.

Update your package.json to do something like this:

"scripts": {
  "build": "node git-version.js && ng build"
}

Then add version-info.ts to the root of your project:

export const versionInfo = (() => {
  try {
    // tslint:disable-next-line:no-var-requires
    return require('../../git-version.json');
  } catch {
    // In dev the file might not exist:
    return { tag: 'v0.0.0', hash: 'dev' };
  }
})();

And import the versionInfo in your app.component.ts or anywhere else you'd want to use it.

Marko Letic
  • 2,460
  • 2
  • 28
  • 34
Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • npm ERR! notsup Unsupported platform for inotify@1.4.2: wanted {"os":"linux","arch":"any"} (current: {"os":"win32","arch":"x64"}) – kinjelom Oct 05 '18 at 09:59
  • Strange. I ran this on my Windows 10 x64 machine without problems, and it runs fine on AppVeyor's VS2017 image too. – Jeroen Oct 09 '18 at 12:09
  • @wildloop The npm syntax to install `git-describe` should be `npm install git-describe --save-dev` – codingbadger Oct 30 '18 at 16:26
16
  1. Add git-version.js to the root.This code will execute git commands and write the output to the git-version.json file.
const childProcess = require('child_process');
const { writeFileSync } = require('fs');

const longSHA = childProcess.execSync("git rev-parse HEAD").toString().trim();
const shortSHA = childProcess.execSync("git rev-parse --short HEAD").toString().trim();
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
const authorName = childProcess.execSync("git log -1 --pretty=format:'%an'").toString().trim();
const commitTime = childProcess.execSync("git log -1 --pretty=format:'%cd'").toString().trim();
const commitMsg = childProcess.execSync("git log -1 --pretty=%B").toString().trim();
const totalCommitCount = childProcess.execSync("git rev-list --count HEAD").toString().trim();

const versionInfo = {
    shortSHA: shortSHA,
    SHA : longSHA,
    branch: branch,
    lastCommitAuthor: authorName,
    lastCommitTime: commitTime,
    lastCommitMessage: commitMsg,
    lastCommitNumber: totalCommitCount
}

const versionInfoJson = JSON.stringify(versionInfo, null, 2);

writeFileSync('/src/git-version.json', versionInfoJson);

This code will generate the git-version.json file :-

{
  "shortSHA": "0e786d4",
  "SHA": "0e786d4ad3778463f6f30c28f254cc85c24eb4b3",
  "branch": "master",
  "lastCommitAuthor": "'saurabh'",
  "lastCommitTime": "'Thu Apr 9 12:59:16 2020 +0530'",
  "lastCommitMessage": "Commit message",
  "lastCommitNumber": "200"
}

Modify above code as per your requirements.

Run: node git-version.js This will generate the git-version.json into src directory.

  1. To run this code before or after the build. add a new script to package.json
"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "build.prod": "node git-version.js && ng build --prod"
  }
  1. Run:- npm run build.prod

suggestions for code improvement are welcome :)

pjpscriv
  • 866
  • 11
  • 20
8

Display branch name and commit hash

I have a little different approach, inspired by answers repo Seba Arce and Jeroen , in main project directory:

  • execute: npm install git-rev-sync --save (this lib give acces to hash and branch name)
  • add file git-version-gen.js with following body

const git  = require('git-rev-sync');
const { writeFileSync } = require('fs');

const gitInfo = { commit: git.short(), commitLong: git.long(), branch: git.branch() };
const ts = 'export const gitVersion = ' + JSON.stringify(gitInfo, null, 2);

writeFileSync('src/environments/git-version.ts', ts);
  • in package.json in scripts add "build": "node git-version-gen.js && ng build ..."
  • in your main app file e.g. app.component.ts use it as follows

import { gitVersion } from '../../../environments/git-version';

// ...

  constructor() {
    console.log(`GIT branch:`,gitVersion.branch);
    console.log(`GIT commit:`,gitVersion.commit);
  }

What are advantages of use this?

  • we create here src/environments/git-version.ts file so this is TypeScript, not .json - similar like you environments files - this will turn on support from your code editor (e.g VSCode)

  • you have acces to commit hash nad branch name (lib git-describe not gives acces to branch name)

  • if you commit generated git-version.ts file instead put it to .gitignore then project will run without build (e.g. by ng serve ...) and fresh developers will not be confused that there is missing some 'mystic' file... - however choice is up to you.

  • cross platform - tested on Azure (windows), MacOs (~linux-like)

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
7

I like to keep things simple. Can add to your index.html:

<script>window.version = '{git-hash}';</script>

Then add a postbuild script to your package.json:

"postbuild": "sed -i '' \"s/{git\\-hash}/$(git rev-parse --short HEAD)/g\" dist/*/index.html"

Not elegant by any means. Personally I create an object on window with various information about the build (time, version, and a release summary link).

To stay more 'pure', stick the {git-hash} string in environment.prod.ts and run the sed against all main-*.js files produced.

"postbuild": "sed -i '' \"s/{git\\-hash}/$(git rev-parse --short HEAD)/g\" dist/*/main-*.js"

Note you'll always see '{git-hash}' running locally as it's only replaced post-build. If you replace it before the build, obviously can't replace it on future builds locally and would most definitely accidentally check that in.

---- UPDATE ----

Ended up creating a library to pull various information. Only ended up using version, build time, and commitTime personally.

https://www.npmjs.com/package/@rippell/ngx-build-info

Rohde Fischer
  • 1,248
  • 2
  • 10
  • 32
Charly
  • 881
  • 10
  • 19
2

I went with a modified version of Vilmantas Baranauskas

I moved src/index.html to src/index.base.html and added an empty <meta name="revision" content=""> inside the HEAD

Example:

<head>
    <meta charset="utf-8">
    <title>MySuperAwesome Angular</title>
    <base href="/">

    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="revision" content="">
    <link rel="icon" type="image/x-icon" href="favicon.ico">

Then modified git.version.ts like this:

import 'rxjs/add/observable/combineLatest';

import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { Observable } from 'rxjs/Observable';

const indexBasePath = join(__dirname, 'src');

const exec = require('child_process').exec;

const revObs = new Observable<string>(s => {
    exec('git rev-parse --short HEAD',
    function (error: Error, stdout: Buffer, stderr: Buffer) {
        if (error !== null) {
            console.log('git error: ' + error + stderr);
        }
        s.next(stdout.toString().trim());
        s.complete();
    });
});

const branchObs = new Observable<string>(s => {
    exec('git rev-parse --abbrev-ref HEAD',
    function (error: Error, stdout: Buffer, stderr: Buffer) {
        if (error !== null) {
            console.log('git error: ' + error + stderr);
        }
        s.next(stdout.toString().trim());
        s.complete();
    });
});

Observable
    .combineLatest(revObs, branchObs)
    .subscribe(([revision, branch]) => {
        console.log(`revision: '${revision}', branch: '${branch}'`);

        const baseHTML = readFileSync(join(indexBasePath, 'index.base.html'), 'utf8');
        const html = baseHTML
          .replace('<meta name="revision" content="">', `<meta name="revision" content="${ revision }">`);

        writeFileSync(
          join(indexBasePath, 'index.html'),
          html,
          { encoding: 'utf8' }
        );
    });

In this example I only put revision, but you can be more thorough and put branch and version inside your html HEAD section

Rohde Fischer
  • 1,248
  • 2
  • 10
  • 32
Valerio
  • 2,390
  • 3
  • 24
  • 34
2

I've done it by generating prebuild script run on postinstall and run before any angular related script

const fs =  require('fs');
const git = require('git-rev-sync');
var mkdirp = require('mkdirp');

const releaseTag = git.tag();
const template = `export const gitTag = '${releaseTag}';\n`;

mkdirp('./generated', function(err) {
    fs.writeFileSync('./generated/git-tag.ts', template, { encoding: 'UTF-8' });
});

which generated git-tag.ts file:

export const gitTag = 'xxxxxxx';

and now u just use in component

import { gitTag } from '[pathToRoot]/generated/git-tag';

also add .gitignore

generated

Rohde Fischer
  • 1,248
  • 2
  • 10
  • 32
repo
  • 748
  • 1
  • 8
  • 19
1

For angular 6

1 Install git-describe as a dev dependency

 npm i git-describe -s

2 On your root project create a grab-git-info.js

   const { gitDescribeSync } = require('git-describe');
   const { writeFileSync } = require('fs');
   const path = require('path');
   const info = gitDescribeSync();
   const infoJson = JSON.stringify(info, null, 2);
   writeFileSync(path.join(__dirname, '/src/git-version.json'), infoJson);

The output of the grab-git-info.js script will be the ‘git-version.json’ file under /src/ which will contain all the git info needed by our app.

In order to be able to import the json file (or any other json file) we need to add a definition file declaring of the added module so that the Typescript compiler will recognize it.

  1. Under your /src create typings.d.ts file (read more about typings.d.ts here: https://angular.io/guide/typescript-configuration#typescript-typings)

/src/typings.d.ts:

 declare module '*.json' {
   const value: any;
   export default value;
 }

From this point on you can import any json file located under /src as a module!

In your component you can import this json

 import * as data from '../../../git-version.json';
 ...
 public git = data;

In the html

 Rev: {{git.hash}}

Finally Add and most important, run the script before build

In package.json add:

"scripts": {
  "ng": "ng",
  "start": "ng serve",
  "build": "node grab-git-info && ng build",

And run the app with

 npm run build
Seba Arce
  • 31
  • 3
0

Use gulp task using gulp-replace and git-rev-sync to add the hash and branch on build :

1) Create the gulp task

var gulp            =    require('gulp'),
    replace         =    require('gulp-replace'),
    git             =    require('git-rev-sync'),

gulp.task('git', function () {
    gulp.src('src/index.html')
        .pipe(replace('{{git-branch}}', git.branch()))
        .pipe(replace('{{git-hash}}', git.short()))
        .pipe(gulp.dest('src/'))
});

// Build Tasks
gulp.task('build', ['git']);

2) Add the following code to index.html :

{{git-branch}}@{{git-hash}}

3) Run

gulp build
abahet
  • 10,355
  • 4
  • 32
  • 23