Solving the complexity of using default-env.json in a project with multiple tiers or targets


Introduction

When I locally want to run a Node.js application that requires one or more services running on SAP BTP, I use a default-env.json file to make these services’ credentials available to the modules in my application. It is named default-env.json by convention, which is determined by the @sap/xsenv package. Modules like @sap/approuter,  @sap-cloud-sdk/, @sap/cds, etc., which are using @sap/xsenv all have a common way to setup the runtime environment with these variables.

It can be cumbersome to switch between different default-env.json files whenever another system (read BTP subaccount) has to be targeted for running the application locally. In this short blog post I will show you how I solve this complexity by using git hooks.

The problem

Imagine having a project that has 3 git branches with the names ‘development’, ‘acceptance’ and ‘production’. These individual branches keep track of the differences in source code between each tier. By default, when making a change to the default-env.json file, the change will be tracked and when merging one branch (development) into another (acceptance), it will overwrite the defaul-env.json with the credentials from ‘development’. To avoid overwriting the changes in the default-env.json files when merging changes, you could look into merge policies using .gitattributes, however this has disadvantages. For example, this would only ignore changes during merge when there are conflicts, but more importantly, that would require committing the default-env.json (containing sensitive credentials) to a source code repository.

Solution: use of a git post-checkout hook

I create a directory ‘./default-envs’ where I put one subfolder for each branch name that I have. Then I create a git post-checkout script that, based on the name of the current branch, will get the correct default-env.json and copy that to the intended location(s) for running the project locally.

Based on the example project above, I would have the following structure for a CAP project:

> sample-project > .githooks - post-checkout > default-envs > development - default-env.json > acceptance - default-env.json > production - default-env.json > app > srv - .gitignore - package.json - README.md

These are the contents of my post-checkout script:

#!/bin/sh BRANCH=`git reflog | awk 'NR==1{ print $8; exit }'` cp "./default-envs/$BRANCH/default-env.json" "./default-env.json"
cp "./default-envs/$BRANCH/default-env.json" "./app/default-env.json" exit 0

Note: by default ‘cp’ will overwrite and that is what we want to happen here.

To make this approach work in a collaborative environment, I move the git hook script from the default ./.git/hooks location to a ./.githooks directory as part of the project structure. To make git aware that it need to look for hooks in that directory I execute this command once:

$ git config --local core.hooksPath .githooks/

I make sure that this instruction of the initial setup is documented in my project’s README.md and to add the following lines to .gitignore:

**/default-env.json
default-envs/*

Conclusion

This setup works very well for my development flow. Let me know in the comments if you were struggling with similar issues or if you have another approach to this that works for you.