Continuous Integration (CI)
Continuous Integration (CI) is a software development practice where developers regularly merge their code changes into a central repository, after which automated builds and tests are run.
Problem
How can you enforce style checks (formatting, linting) and ensure all tests pass whenever your team pushes code?
If you’ve completed our earlier Todo App Course, you’ve seen how we use Husky and lint-staged to enforce Prettier and ESLint on staged files through Git hooks.
However, Git hooks can be bypassed—either by skipping installation or using the --no-verify flag. To make checks mandatory, we can delegate them to our Git repository platform (like GitHub).
This is where GitHub Actions Workflows come in, allowing us to automate these checks and truly continuously integrate changes into the main repository.
Project Structure
A simplified overview of the project:
We won’t modify much of the app’s source code—just the configuration needed for GitHub Actions workflows.
The CI Pipeline
Create a new branch named ci-integration and publish it.
We’ll set up two jobs that run whenever a pull request targets the default branch (0-init):
- Style checks — Prettier + ESLint
- Automated tests — Vitest
Start by creating a .github directory with a workflows subfolder, then add a file named .github/workflows/ci.yml.
- name(optional) defines the workflow’s display name.
- onspecifies the GitHub event that triggers the workflow. Here, it runs on every pull request targeting- 0-init.
1. Formatting Checks with Prettier
We’ll first set up a job to verify code formatting using Prettier.
Add two new scripts to your package.json:
These scripts either check or fix code formatting. Now, let’s tell GitHub how to run them.
We’ll use a GitHub-hosted runner — a virtual machine that executes your workflow steps.
This uses the latest Ubuntu runner. Next, we’ll define the steps that run within this job.
Steps Overview
If you were running this locally, you’d:
- Check out the repository code
- Set up Node.js and pnpm
- Install dependencies
- Run Prettier
Let’s translate that into GitHub Actions steps.
1. Checkout Code
We’ll use the official checkout action:
2. Set Up Environment
We’ll use public actions for both pnpm and Node.js:
It’s best practice to specify a Node version explicitly for consistency.
3. Install Dependencies
4. Run Formatting Check
Complete Style Job
Here’s the full configuration:
Push these changes to your ci-integration branch and open a pull request.
You’ll see a workflow named ci / Style (pull_request) start running—and if your code is formatted correctly, it’ll pass ✅.
Always test for failing builds
It is quite easy to assume the action works as expected but testing for failing builds is a better validation. So, just try to commit an inconsistently formatted code and check if the build failed or not!
Now that our formatting check is automated, we can move on to linting with ESLint and automated testing next.
2. Linting with ESLint
This is quite simple. Just add another step to the existing style job.
Push your code, and you’ll notice that the action takes a little longer—but then fails! That’s actually a good thing. The logs will show two errors related to the react-refresh/only-export-components rule.
Here’s what’s happening: shadcn exports all component-related logic from a single file, but in our case, vite struggles with Hot Module Replacement (HMR) and recommends moving those exports into separate files. This rule makes sense in general, but for certain files that don’t change frequently, keeping everything in one file can actually improve readability and maintainability.
So, we will just setup our eslint.config.js to ignore this rule for the shadcn component ui directory.
References: I know this because I searched the internet!
Push this change, and our action should pass with only warnings.
I think warnings are okay!
This is my personal opinion, and I would not check for something like eslint   . --max-warnings=0. You should ideally talk to your team and decide.
3. Automated Testing
Great, let us now set up another job for tests. Though we can add another step for tests in a single job. It would mean a sequential run. Do we really care that tests should only run after the style check? No, it can run in parallel, and whichever fails first is a better feedback.
We just repeat the steps and just run our test script.
Always test for failing builds
I repeat again! It is quite easy to assume the action works as expected but testing for failing builds is a better validation. So, intentionally break a test and check for correct failure.
At this point, our code should match the code in the branch 1-ci-setup.
Last updated on
Overview
This is simple to learn, and by the end of this short course you’ll be much more confident dealing with GitHub Actions.
Continuous Deployment (CD)
While CI ensures that every code change is automatically built, tested, and verified, CD takes it further—automatically deploying those verified changes to production (or staging) environments.