Tiago Fortunato
ProjectsOdysDeployment

CI Pipeline

CI pipeline: typecheck + lint + build

CI Pipeline

The ci.yml workflow defines the Continuous Integration (CI) pipeline for the Odys project, residing in the .github/workflows/ci.yml file. This pipeline is a critical component of the development process, designed to automatically verify the quality and buildability of the codebase whenever changes are introduced. By running a series of automated checks, it helps maintain code health, catch errors early, and ensure that the application remains in a deployable state.

Overview

This GitHub Actions workflow is configured to trigger automatically on two primary events:

  • Every push to the main branch, ensuring that all merged changes are validated.
  • Every pull_request targeting the main branch, providing immediate feedback on proposed changes before they are integrated.

The workflow is structured into two distinct jobs: check and build. This separation of concerns allows for a clear progression of validation steps. The build job is explicitly configured to needs: check, meaning it will only execute if the check job completes successfully. This dependency ensures that no code is built unless it first passes all static analysis and tests, saving computational resources and providing a more reliable build artifact.

The check Job: Typecheck, Lint & Tests

The check job, aptly named "Typecheck, Lint & Tests", is the first line of defense for code quality. It runs on an ubuntu-latest environment, providing a consistent execution context.

The steps within this job are:

  1. Checkout Code: The actions/checkout@v6 action fetches the repository's code, making it available for subsequent steps.
  2. Setup Node.js: The actions/setup-node@v6 action configures the environment with Node.js version 20. It also utilizes npm caching, which significantly speeds up subsequent workflow runs by reusing downloaded dependencies.
  3. Install Dependencies: The npm ci command installs project dependencies. Using npm ci (clean install) instead of npm install is a deliberate choice for CI environments, as it guarantees that the exact versions specified in package-lock.json are installed, leading to more reproducible builds.
  4. Typecheck: This step executes npx tsc --noEmit. The TypeScript compiler (tsc) is run to perform static type analysis across the entire codebase. The --noEmit flag is crucial here; it instructs TypeScript to only check for type errors without generating any JavaScript output files. This makes the step faster and ensures it focuses solely on type correctness.
  5. Lint: The npm run lint command invokes the project's linter. This step enforces coding standards, identifies stylistic issues, and catches potential programming errors, contributing to a more consistent and maintainable codebase.
  6. Unit tests: Finally, npm test runs the project's suite of unit tests. This step verifies the correctness of individual components and functions, ensuring that recent changes have not introduced regressions.

The build Job: Application Compilation

The build job, named "Build", is responsible for compiling the application into a deployable artifact. It also runs on ubuntu-latest and, as mentioned, needs the check job to pass before it begins.

A key aspect of the build job is the extensive list of environment variables it requires. These variables, sourced from GitHub Secrets, are essential for the build process to correctly configure the application for various services and integrations. They include:

  • NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY for Supabase integration.
  • SUPABASE_SERVICE_ROLE_KEY and DATABASE_URL for database access.
  • STRIPE_SECRET_KEY, STRIPE_BASIC_PRICE_ID, STRIPE_WEBHOOK_SECRET, and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY for Stripe payment processing, including the STRIPE_BASIC_PRICE_ID which corresponds to the basic plan's pricing.
  • EVOLUTION_API_URL, EVOLUTION_API_KEY, and EVOLUTION_INSTANCE for the Evolution API, likely related to WhatsApp integration given the project's use of 19 WhatsApp message templates.
  • CRON_SECRET for securing cron job endpoints like /api/cron/reminders and /api/cron/whatsapp-watchdog.
  • RESEND_API_KEY for email services.
  • UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN for Upstash Redis, which is used for rate limiting across 6 different categories, including booking and aichat.
  • SENTRY_AUTH_TOKEN for error monitoring.

The steps within the build job are similar to the initial setup of the check job:

  1. Checkout Code: actions/checkout@v6 retrieves the source code.
  2. Setup Node.js: actions/setup-node@v6 configures Node.js version 20 and npm caching.
  3. Install Dependencies: npm ci installs dependencies reproducibly.
  4. Build: The npm run build command executes the project's build script. This typically involves transpiling code, optimizing assets, and generating the final output files ready for deployment.

Design Decisions

The structure of this CI pipeline reflects several intentional design choices aimed at efficiency, reliability, and maintainability:

  • Sequential Job Execution (needs: check): By making the build job dependent on the check job, the pipeline ensures that resources are not wasted on building code that already contains errors. This "fail fast" approach provides quicker feedback to developers and reduces overall CI costs.
  • Dedicated check Job for Fast Feedback: Separating typechecking, linting, and testing into a distinct check job allows for rapid validation of code quality. Developers receive quick feedback on common issues without waiting for a full application build, which is often a more time-consuming process.
  • npm ci for Reproducibility: The use of npm ci instead of npm install is a standard practice in CI environments. It guarantees that the exact dependency tree defined in package-lock.json is used, preventing unexpected build failures due to new or updated package versions.
  • tsc --noEmit for Type Safety: Running TypeScript with --noEmit in the check job ensures that type correctness is verified without the overhead of generating JavaScript files. This keeps the check job focused and efficient.
  • Comprehensive Environment Variables for build: Providing a complete set of environment variables to the build job ensures that the application is compiled with all necessary configurations for its various integrations, such as Supabase, Stripe, and WhatsApp. This prevents issues that might arise from missing configuration during the build phase.

Potential Improvements

While the current CI pipeline provides a solid foundation for code quality and build verification, several opportunities exist to enhance its efficiency and coverage:

  1. Parallelize Independent Checks: The Typecheck, Lint, and Unit tests steps within the check job currently execute sequentially. Since these steps are largely independent of each other, they could be configured to run in parallel. This would involve restructuring the check job to leverage GitHub Actions' ability to run steps concurrently, significantly reducing the overall execution time of the check phase.
  2. Integrate Drizzle Schema Validation: The project uses Drizzle ORM, managing 10 tables including professionals, appointments, and clients. Adding a step to validate the Drizzle schema against the database or generated types (e.g., using a drizzle-kit check command if available) within the check job could catch schema drift or definition errors early. This would prevent potential runtime issues related to database schema mismatches before deployment.
  3. Run npm audit for Dependency Security: The project relies on 35 direct dependencies and 11 development dependencies. Incorporating an npm audit step into the check job would automatically scan for known security vulnerabilities in the dependency tree. This provides an additional layer of security assurance, identifying potential risks in third-party packages before the code is built or deployed.

References

  • .github/workflows/ci.yml

On this page