Ensuring code standards in your code repositories can be tricky, there are a few solutions that you can employ such as using husky
hooks together with a tool like lint-staged
. However, there may be situations where the hooks are not enabled on the engineer's machine which will result in unformatted code being committed. Another possibility is when the committer commits the code by using the --no-verify
flag. This will allow the checks to be bypassed.
Adding a job onto the GitLab CI is a great way to ensure that bad code does not make it into the repository, and it will fail the pipeline if the code cannot be fixed automatically by eslint
or prettier
.
Solve
This job works on the assumption that the project should have the following scripts in package.json
and that you have both eslint
and prettier
configured.
The configuration below ignores all files that are in the .gitignore
and only picks up the following file extensions.
extension | eslint | prettier |
---|---|---|
.js | ✅ | ✅ |
.jsx | ✅ | ✅ |
.ts | ✅ | ✅ |
.tsx | ✅ | ✅ |
.gql | ✅ | ❌ |
.graphql | ✅ | ❌ |
"scripts": {
"lint": "eslint --ignore-path .gitignore . --ext .js --ext .jsx --ext .ts --ext .tsx --ext .gql --ext .graphql",
"format": "prettier --ignore-path .gitignore \"./**/*.+(ts|js|tsx|jsx)\" --write"
}
There is some configuration involved on GitLab that you'll have to do at a group or project level. There is a need to create a bot user account, set up the bot account with ssh keys and add the SSH private key to the variable named CI_SSH_PRIVATE_KEY
at a group or project level.
If you're using the following job on SaaS (gitlab.com) and have GitLab Premium, you should be able to use group access tokens or project access tokens in place of having to create a separate user account.
If the job is used on a self-managed instance, the free tier should have access to group and project access tokens regardless in the event that you prefer to do that.
The options in the job that you may want to configure are lines 12
, 52
, and 53
.
Line 12
is the commit message that you would like to use for the jobLine 52
is the email that you want to use for the commit authorLine 53
is the name that you want to use for the commit author
The script
section does the following:
- Configures the default job exit code
- Configures the remote and pulls the code for the branch that the pipeline is running on
- Checks if the current commit is the same as the automated lint job, exits the job if it is
- Install
node_modules
- Lint and Format Code
- Checks if there are any changes to commit, exit if there is nothing
- If there are changes to be committed, create a commit and push it to the repository
- Assign
SKIP_FUTURE_STAGES
totrue
and a random exit code toCI_JOB_SKIP_EXIT_CODE
so we can use the values in future stages to determine if we can end the job earlier
stages:
- clean
- build
lint_job:
image: node:16.17.0-alpine
stage: clean
tags:
- docker
rules:
- if: $CI_COMMIT_BRANCH
variables:
LINT_COMMIT_MESSAGE: "style: lint code by bot"
before_script:
##
## Install ssh-agent if not already installed, it is required by Docker.
## (change apt-get to yum if you use an RPM-based image)
##
- 'which ssh-agent || ( apk --update add git openssh-client )'
##
## Run ssh-agent (inside the build environment)
##
- eval "$(ssh-agent -s)"
##
## Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
## We're using tr to fix line endings which makes ed25519 keys work
## without extra base64 encoding.
## https://gitlab.com/gitlab-examples/ssh-private-key/issues/1#note_48526556
##
- echo "$CI_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
##
## Create the SSH directory and give it the right permissions
##
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
##
## Alternatively, use ssh-keyscan to scan the keys of your private server.
## Replace example.com with your private server's domain name. Repeat that
## command if you have more than one server to connect to.
##
- touch ~/.ssh/known_hosts
- ssh-keyscan $CI_SERVER_HOST >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
##
## Optionally, if you will be using any Git commands, set the user name and
## and email.
##
- git config --global user.email "[email protected]"
- git config --global user.name "Example CI Bot"
script:
- CI_JOB_SKIP_EXIT_CODE=0
- git remote set-url origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
- git fetch
- git checkout -B $CI_COMMIT_REF_NAME
- |
if [ "$CI_COMMIT_MESSAGE" == "$LINT_COMMIT_MESSAGE" ] ; then
echo "Linting code is not required. Skipping this job!"
echo "SKIP_FUTURE_STAGES=false" > build.env
exit ${CI_JOB_SKIP_EXIT_CODE:-0}
fi
- yarn install --frozen-lockfile --prefer-offline --cache-folder .yarn
- echo "Linting Code"
- yarn lint --fix || true
- echo "Formatting Code"
- yarn format || true
- |
if GIT_CHANGES=$(git status --porcelain) && [ -z "$GIT_CHANGES" ]; then
echo "Nothing to lint. Skipping this job!"
echo "SKIP_FUTURE_STAGES=false" > build.env
exit ${CI_JOB_SKIP_EXIT_CODE:-0}
fi
- git add .
- git commit -m "$LINT_COMMIT_MESSAGE" -n
- git push -u origin $CI_COMMIT_REF_NAME
- echo "SKIP_FUTURE_STAGES=true" > build.env # Setting variable for stages so that we can skip them in the next stage
- echo "CI_JOB_SKIP_EXIT_CODE=20" >> build.env # Arbitrary exit code so that future jobs fail and don't waste runner resources
- exit $CI_JOB_SKIP_EXIT_CODE
artifacts:
expire_in: 1 hour
reports:
dotenv: build.env
build_job:
stage: build
script:
- echo "Build Job"
- |
if [ "$SKIP_FUTURE_STAGES" == "true" ] ; then
echo "Skipping job due to variable"
exit ${CI_JOB_SKIP_EXIT_CODE:-0}
fi
needs:
- job: lint_job
artifacts: true
Disclaimer: This post was written when I was employed at GitLab. The content written above was done in my individual capacity.
Leave a comment