End-to-End DevOps with ESLint, Prettier, Husky, GitHub Actions, CI/CD, and Vercel

End-to-End DevOps with ESLint, Prettier, Husky, GitHub Actions, CI/CD, and Vercel

·

6 min read

  1. Start by creating a new nextjs project

  2. Now let’s install everything.

      npm install --save-dev prettier eslint-config-prettier husky lint-staged eslint-plugin-prettier
    

  3. Now let’s create three files: .prettierignore, .eslintrc.json, and .prettierrc.

  4. Add this to your .eslintrc.json.

     {
       "extends": ["eslint:recommended", "prettier", "next", "next/core-web-vitals"],
       "plugins": ["prettier"],
       "rules": {
         "prettier/prettier": "error"
       }
     }
    
  5. Add this to your .prettierignore.

     #Ignore artifacts:
     dist
     .next/
     public/
     build
    
     #Ignore specific files:
     package-lock.json
    
  6. Add this to your .prettierrc.

     {
       "trailingComma": "es5",
       "useTabs": false,
       "tabWidth": 4,
       "semi": true,
       "singleQuote": true,
       "printWidth": 80,
       "bracketSpacing": true
     }
    
  7. Now let's open package.json and write some scripts.

     "scripts": {
             "dev": "next dev",
             "build": "next build",
             "start": "next start",
             "lint": "eslint src --report-unused-disable-directives --max-warnings 0",
             "lint:fix": "eslint src --fix",
             "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,html}'"
     }
    
  8. Now let’s open eslint.config.js to tell ESLint that we only want to lint files inside the src folder and add files: ['src/**/*.{ts,tsx,js,jsx}']. Your ESLint config array will look like this now:

     const eslintConfig = [
         ...compat.extends('next/core-web-vitals', 'next/typescript'),
         {
             files: ['src/**/*.{ts,tsx,js,jsx}'],
         },
     ];
    
  9. Now let’s push our code to GitHub. The reason I’m doing this is that I want to show you how it will behave before and after configuring Husky.

    Here, I’ve created a new GitHub repo and pushed everything there.

  10. Now add hooks in the package.json file in the root directory alongside scripts and dependencies.

     "husky": {
            "hooks": {
                "pre-commit": "lint-staged",
                "pre-push": "npm run lint && npm run format"
            }
        },
        "lint-staged": {
            "src/**/*.{ts,tsx,js,jsx}": [
                "npm run lint",
                "npm run format"
            ]
    }
    
  11. Now run the npx husky init command. It will add a new folder to your project and add a new script to your package.json.

  12. Now add npx lint staged in your pre-commit file like this

  13. Now let’s run git add . and git commit -m '3rd commit' to check if linting and formatting work.

    Now let’s check by adding an unused variable in page.tsx inside the app folder to see if our linting works.

    Run git add . and git commit -m 'commit to check linting' again.

    See the error: a is assigned but never read, and it is stopping us from committing.

    We can run npm run lint:fix to fix 90% of the linting errors because we’ve set up a script for that as well.

    Now, let’s remove the unused variable and try to commit and push.

  14. Now, set the commit message policies so that people who push code to your repo follow certain commit message rules.

    Install these two packages:

    npm install --save-dev @commitlint/cli
    npm install --save-dev @commitlint/config-conventional
    

  15. Now create a new file in the .husky folder and add:

    npx --no-install commitlint --edit "$1"
    
  16. Create a new file commitlint.config.cjs in the root of the project and add this to your file:

    module.exports = {
        // extends: ['@commitlint/config-conventional'],
        extends: [],
        rules: {
          'header-min-length': [2, 'always', 10],
          'header-case-start-capital': [2, 'always'],
          'header-end-period': [2, 'always'],
        },
        plugins: [
          {
            rules: {
              'header-case-start-capital': ({ raw }) => {
                return [
                  /^[A-Z]/.test(raw),
                  'Commit message must start with a capital letter',
                ];
              },
              'header-end-period': ({ header }) => {
                return [/\.$/.test(header), 'Commit message must end with a period'];
              },
            },
          },
        ],
      };
    

    This will ensure various things, such as: 'commit message must start with a capital letter,' 'should have a length of 10,' and 'ends with a full stop.'

  17. Now let’s try to push something with a wrong commit message. Make sure you change something inside the src folder to trigger the hooks.

    And now, let’s fix the message and see.

  18. Now let’s configure a CI pipeline. Create a .github folder and a workflows folder inside it. Then, create a ci.yml file inside the workflows folder.

    Paste this content in the file. You can ask ChatGPT to explain this file to you; it’s quite simple, to be honest.

    name: CI workflow for Blog
    
    on:
        push:
        pull_request:
    
    jobs:
        build:
            runs-on: ubuntu-latest
    
            steps:
                - uses: actions/checkout@v2
                  with:
                      fetch-depth: 0 # Fetch all history for all branches and tags
                - name: Use Node.js
                  uses: actions/setup-node@v2
                  with:
                      node-version: '20.17.0'
    
                - name: Install dependencies
                  run: npm install
    
                - name: Run lint
                  run: npm run lint
    
                - name: Run format check
                  run: npm run format
    
                - name: Check commit messages
                  uses: wagoid/commitlint-github-action@v3
                  with:
                      configFile: commitlint.config.cjs
    
  1. Now let’s test the CI pipeline we’ve created.

    And let’s open the Actions tab in the GitHub repo.

    We passed all the checks! LFG

    Let’s try to learn about some warnings here (skip if you want). This wasn’t planned, but let’s address it and maybe upgrade our script according to them.

    Warning 1: Ubuntu-latest pipelines will use ubuntu-24.04 soon

    Details: GitHub is planning to update the default ubuntu-latest runner to use ubuntu-24.04. Currently, it likely uses ubuntu-22.04.

    This means that your workflow might soon start using Ubuntu 24.04 instead of the current version.

    Warning 2: The set-output command is deprecated

    Details: The set-output command was previously used in GitHub Actions to pass data between steps.

    GitHub has replaced this functionality with environment files, which are more secure and performant.

    If your workflow uses an action or script that still relies on set-output, it will need to be updated.

    Fixing these using the new file—check code comments for changes.

    name: CI workflow for Blog
    
    on:
        push:
        pull_request:
    
    jobs:
        build:
            runs-on: ubuntu-22.04 #replaced the latest to 22.04
    
            steps:
                - uses: actions/checkout@v4 #updated version
                  with:
                      fetch-depth: 0 
                - name: Use Node.js
                  uses: actions/setup-node@v4 #updated version
                  with:
                      node-version: '20.17.0'
    
                - name: Install dependencies
                  run: npm install
    
                - name: Run lint
                  run: npm run lint
    
                - name: Run format check
                  run: npm run format
    
                - name: Check commit messages
                  uses: wagoid/commitlint-github-action@v6 #updated version
                  with:
                      configFile: commitlint.config.cjs
    

    So we are done here, and now let’s wrap up this blog by building a CD pipeline.

  2. Go inside the workflows folder and create a new file called deploy.yml. In this project, we will deploy to Vercel. Vercel deployment is really easy, but I want to give a sample of how things work here.

    name: CD Pipeline to Vercel
    
    on:
        push:
    
    jobs:
        deploy:
            runs-on: ubuntu-22.04
    
            steps:
                - name: Checkout Repository
                  uses: actions/checkout@v4
    
                - name: Install Vercel CLI
                  run: npm install --global vercel@latest
    
                - name: Pull Vercel Environment Information
                  run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
    
                - name: Build Project Artifacts
                  run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
    
                - name: Deploy Project Artifacts to Vercel
                  run: vercel deploy --yes --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
    

    Now go to Vercel and get an access token. Name it VERCEL_TOKEN and paste this token in the GitHub repository secrets.

    And now, push the code to GitHub and see the magic happen in the Actions tab.