Improving Git Flow for Managing Private TypeScript Dependencies on Bitbucket

In our current workflow, Bitbucket serves both as a code repository and a private NPM repository. Installing dependencies via git+ssh and specifying a tag is very convenient for pure JavaScript projects.

As some dependencies started using TypeScript, we need to manage them in one of the following ways:

  1. Directly commit the compiled dist files to the main/master branch.
  2. Compile during postinstall. Upon dependency installation, it automatically compiles.

However, both methods have their drawbacks.

  • Directly committing dist to the main branch adds many extra files during code review, making the project structure less elegant.
  • Using postinstall requires installing typescript additionally for pure JavaScript projects, which is also not elegant.

Considering the manpower and repository migration costs (we have nearly 100 dependencies, each with many versions and used by different services), we can only address these issues by improving Git flow.

Objectives

The redesigned Git flow should meet the following objectives:

  1. Development branches should not contain compiled dist files.
  2. Compilation should not rely on postinstall.
  3. Dependency installation should remain unchanged, still via git+ssh and specifying tag.
  4. Consideration for hotfix scenarios.

Improved Git Flow

To achieve the above objectives, here is the improved Git flow:
Improved Git Flow

For regular development, after merging code into master, we first push the src tag and modify .gitignore to temporarily create a build branch. Then, we compile on the build branch and tag it formally, including the dist files, which can be referenced by other services.

When a hotfix is needed, we can branch off from the corresponding src tag to fix and then merge back into the master branch following the same steps.

Pipeline Script Configuration

The Git flow described above involves tagging, modifying .gitignore, compiling, and then releasing after code merges. Even with documentation, it’s prone to errors in practice. Thus, it’s crucial to automate these steps using Bitbucket Pipeline upon merging code into the master branch.

Here is an example:

  1. Pipeline section:
1
2
3
4
5
6
7
8
branches:
master: # Trigger only when merging into the main branch
- step:
name: build
deployment: Build # Execute as Deployment, the name can be arbitrary
script:
- git remote set-url origin ${BITBUCKET_GIT_SSH_ORIGIN}
- ./scripts/build.sh
  1. Tagging and compilation script:

The build.sh script in the pipeline is the core of the Git flow. It tags the source, compiles, and pushes the compiled formal tag.

The version of the tag follows semver rules, with the current tag being fetched from package.json’s version. The next version is derived from the prefix of the branch name, e.g., for major/xxxx, the next version increments the major version; for feature/xxx, it increments the minor version. It uses npm version command to upgrade the tag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env bash

set -e
# Create tags in the bitbucket pipline, env from bitbucket pipeline
source_branch=$(git log --format=%B -n 1 $BITBUCKET_COMMIT | awk '{print $3}')

if [[ -n $source_branch ]]; then
# PART 1: Get the next tag
echo "Triggered by pull request ${source_branch}"

if [[ ${source_branch} == feature/* ]]; then
level=minor
elif [[ ${source_branch} == bugfix/* ]]; then
level=patch
elif [[ ${source_branch} == hotfix/* ]]; then
level=prerelease
elif [[ ${source_branch} == major/* ]]; then
level=major
else
echo "Nothing happen on branch: ${BITBUCKET_BRANCH}"
echo "Source branch: ${source_branch}"
fi

if [[ ! -z ${level} ]]; then
# PART 2: Create src-tag and push back
current_version=$(node -e "console.log(require('./package.json').version)") && echo "Current version - ${current_version}"
echo "Crated version on branch: ${BITBUCKET_BRANCH}. Tag: ${current_version}"

next_version=$(npx semver ${current_version} -i ${level})
source_tag=v${next_version}-src

echo "Build source tag: ${source_tag} on branch: ${BITBUCKET_BRANCH}"
# Create the source tag and push back
npm version ${next_version} --no-git-tag-version # This will not create a git tag just update the version in the package.json

git add --all
git commit -m "[skip ci] ${current_version} --> ${next_version}" # [skip ci] Will not trigger this pipeline again
git tag -am "[skip ci] source tag: ${source_tag}" ${source_tag}
git push origin ${BITBUCKET_BRANCH} ${source_tag}

# PART 3: Add the dist folder and create release tag
# Add the dist folder
sed -i 's/dist//g' .gitignore

# build ts
npm run build

# push to tag
git add --all
git commit -m "[skip ci] Build release tag: ${next_version} on branch: ${BITBUCKET_BRANCH}"
git tag -am "[skip ci] release tag: ${next_version}" ${next_version} # only push the tag
git push origin refs/tags/${next_version}
fi
fi

It’s recommended to execute Shell scripts within the Deployment section because it’s a blocking queue, ensuring only one script runs at a time, thus avoiding tag duplication issues.

Also, certain naming restrictions should be imposed on development branches, for instance, using tools like Husky.

Expansion

This Git flow can be applied in any code hosting tool, even without triggering methods like Pipeline/GitAction; it can still be achieved by running scripts. It also enables automatic tagging.

Although there’s no perfect Git flow, a suitable one can standardize our work and enhance efficiency.

(Translated by ChatGPT)


Improving Git Flow for Managing Private TypeScript Dependencies on Bitbucket
https://konta9.github.io/en/2024/02/11/2024/improve-git-flow-to-manage-ts-dependency-on-bitbucket-as-private-npm/
Author
Konata
Posted on
February 11, 2024
Licensed under