Добавляем lint-staged в NestJS и Angular приложения, включаем семантическое версионирование фронтенда

b171845bf01e4451025312e0645204dc.png

Предыдущая статья: Семантическое версионирование NestJS и Angular приложений в NX-монорепозитории

Так как версионирование через плагин nx-semantic-release происходит путем анализа изменений по связанным Typescript-импортам, то нам нужно минимизировать эти изменения, для этого в проект подключаем https://www.npmjs.com/package/lint-staged и добавляем строгости в Typescript-код.

1. Добавляем lint-staged для форматирования кода при коммите

Эта утилита запускает определенные скрипты при каждом коммите, для того чтобы форматирование кода в git-репозитории было всегда одинаковым и не важно как именно разработчик настроил свою локальную среду разработки.

Команды

npx mrm@2 lint-staged

Вывод консоли

$ npx mrm@2 lint-staged
Running lint-staged...
Update package.json
Installing husky...

added 1 package, removed 1 package, and audited 2765 packages in 18s

331 packages are looking for funding
  run `npm fund` for details

49 vulnerabilities (31 moderate, 18 high)

To address issues that do not require attention, run:
  npm audit fix

To address all issues possible (including breaking changes), run:
  npm audit fix --force

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.
husky - Git hooks installed
husky - created .husky/pre-commit

2. Обновляем prepare скрипт и секцию lint-staged в корневом package.json

Скрипт prepare автоматически появляется после установки lint-staged, я не стал его убирать просто немного изменил способ запуска, запускаю через npx.

В небольших проектах pre-commit-хук с lint-staged отрабатывает быстро, но если проект большой то он может работать дольше, в таком случаи проще всем разработчикам договориться об общем стиле форматирования, для того чтобы уменьшить количество файлов которые необходимо будет проверить линтерам.

В pre-commit-хук не стоит прописывать различные тяжелые операции, например: генерацию фронтенд клиента, такие операции лучше производить в CI/CD или локально руками по необходимости, а не на каждый коммит.

Обновляем часть файла package.json

{
  "scripts": {
    // ...
    "prepare": "npx -y husky install"
    // ...
  },
  // ...
  "lint-staged": {
    "*.{js,ts}": "eslint --fix",
    "*.{js,ts,css,scss,md}": "prettier --ignore-unknown --write",
    "*.js": "eslint --cache --fix"
  }
  // ...
}

3. Запускаем форматирование lint-staged-ом вручную

Для того чтобы можно было вручную проверить работу lint-staged необходимо добавить все файлы в stage запустить его через npx.

Команды

git add .
npx lint-staged

Вывод консоли

 npx lint-staged
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...

4. Обновляем package.json и NX-конфигурацию в бэкенд приложении

Так как в предыдущем посте мы отключали публикацию в npm, то у нас не происходила смена версии приложения в исходном коде, для того чтобы версия в исходном коде сменилась и при этом публикация в npm не запускалась, нужно добавить опцию "private": true.

Обновляем файл apps/server/package.json

{
  "name": "server",
  "version": "0.0.3",
  "private": true,
  "scripts": {},
  "dependencies": {
    "pm2": ">=5.3.0",
    "dotenv": ">=16.3.1"
  },
  "devScripts": ["manual:prepare", "serve:dev:server"],
  "prodScripts": ["manual:prepare", "start:prod:server"],
  "testsScripts": ["test:server"]
}

Обновляем часть файла apps/server/package.json

{
  "name": "server",
  // ...
  "targets": {
    // ...
    "semantic-release": {
      "executor": "@theunderscorer/nx-semantic-release:semantic-release",
      "options": {
        "github": true,
        "changelog": true,
        "npm": true,
        "tagFormat": "server-v${VERSION}"
      }
    }
  }
}

5. Создаем package.json в фронтенд приложении и добавляем команду semantic-release в его NX-конфигурацию

Ранее в постах мы запускали передеплой Nginx при изменениях версии бэкенд приложения.

Для того чтобы Nginx-образ с встроенным фронтендом собирался только при изменениях фронтенда нам нужно версионировать фронтенд и использовать его версию в дальнейших логиках с Docker-образами и Kubernetes-шаблонами.

Для работы семантического версионирования необходимо наличие package.json у библиотеки или приложения, поэтому мы добавляем его в фронтенд приложение и указываем "private": true.

Создаем файл apps/client/package.json

{
  "name": "client",
  "version": "0.0.1",
  "private": true
}

Добавляем новый таргет в файл apps/client/project.json

{
  "name": "client",
  // ...
  "targets": {
    // ...
    "semantic-release": {
      "executor": "@theunderscorer/nx-semantic-release:semantic-release",
      "options": {
        "github": true,
        "changelog": true,
        "npm": true,
        "tagFormat": "client-v${VERSION}"
      }
    }
  }
}

6. Добавляем новую динамическую переменную окружения

Добавляем новую переменную с версией фронтенд приложения в файл .kubernetes/set-env.sh и .docker/set-env.sh

export CLIENT_VERSION=$(cd ./apps/client && npm pkg get version --workspaces=false | tr -d \")

7. Обновляем деплоймент файл

Обновляем файл .kubernetes/templates/client/3.deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: '%NAMESPACE%'
  name: %NAMESPACE%-client
spec:
  replicas: 1
  selector:
    matchLabels:
      pod: %NAMESPACE%-client-container
  template:
    metadata:
      namespace: '%NAMESPACE%'
      labels:
        app: %NAMESPACE%-client
        pod: %NAMESPACE%-client-container
    spec:
      containers:
        - name: %NAMESPACE%-client
          image: ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx:%CLIENT_VERSION%
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: %NGINX_PORT%
          envFrom:
            - configMapRef:
                name: %NAMESPACE%-config
            - configMapRef:
                name: %NAMESPACE%-client-config
          resources:
            requests:
              memory: 128Mi
              cpu: 100m
            limits:
              memory: 512Mi
              cpu: 300m
      imagePullSecrets:
        - name: docker-regcred

8. Обновляем CI/CD-конфигурацию деплоя для Kubernetes и «Docker Compose»

Обновляем часть файла .github/workflows/kubernetes.yml и .github/workflows/docker-compose.workflows.yml

jobs:
  # ...
  check-nginx-image:
    runs-on: ubuntu-latest
    needs: [release]
    continue-on-error: true
    steps:
      - name: Checkout repository
        if: ${{ !contains(github.event.head_commit.message, '[skip cache]') && !contains(github.event.head_commit.message, '[skip nginx cache]') }}
        uses: actions/checkout@v4
      - name: Set ENV vars
        if: ${{ !contains(github.event.head_commit.message, '[skip cache]') && !contains(github.event.head_commit.message, '[skip nginx cache]') }}
        id: version
        run: |
          echo "client_version="$(cd ./apps/client && npm pkg get version --workspaces=false | tr -d \") >> "$GITHUB_OUTPUT"
      - name: Check exists docker image
        if: ${{ !contains(github.event.head_commit.message, '[skip cache]') && !contains(github.event.head_commit.message, '[skip nginx cache]') }}
        id: check-exists
        run: |
          export TOKEN=$(curl -u ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} https://${{ env.REGISTRY }}/token\?scope\="repository:${{ env.NGINX_IMAGE_NAME}}:pull" | jq -r .token)
          curl --head --fail -H "Authorization: Bearer $TOKEN" https://${{ env.REGISTRY }}/v2/${{ env.NGINX_IMAGE_NAME}}/manifests/${{ steps.version.outputs.client_version }}
      - name: Store result of check exists docker image
        id: store-check-exists
        if: ${{ !contains(github.event.head_commit.message, '[skip cache]') && !contains(github.event.head_commit.message, '[skip nginx cache]') && !contains(needs.check-exists.outputs.result, 'HTTP/2 404') }}
        run: |
          echo "conclusion=success" >> "$GITHUB_OUTPUT"
    outputs:
      result: ${{ steps.store-check-exists.outputs.conclusion }}
  # ...
  build-and-push-nginx-image:
    runs-on: ubuntu-latest
    needs: [build-and-push-builder-image, check-nginx-image]
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    steps:
      - name: Checkout repository
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        uses: actions/checkout@v4
      - name: Set ENV vars
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        id: version
        run: |
          echo "root_version="$(npm pkg get version --workspaces=false | tr -d \") >> "$GITHUB_OUTPUT"
          echo "client_version="$(cd ./apps/client && npm pkg get version --workspaces=false | tr -d \") >> "$GITHUB_OUTPUT"
      - name: Log in to the Container registry
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Generate and build production code
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        run: |
          mkdir -p dist
          docker run -v ./dist:/usr/src/app/dist -v ./apps:/usr/src/app/apps -v ./libs:/usr/src/app/libs ${{ env.REGISTRY}}/${{ env.BUILDER_IMAGE_NAME}}:${{ steps.version.outputs.root_version }}
      - name: Build and push Docker image
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        id: push
        uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
        with:
          context: .
          push: true
          file: ./.docker/nginx.Dockerfile
          tags: ${{ env.REGISTRY}}/${{ env.NGINX_IMAGE_NAME}}:${{ steps.version.outputs.client_version }},${{ env.REGISTRY}}/${{ env.NGINX_IMAGE_NAME}}:latest
          cache-from: type=registry,ref=${{ env.REGISTRY}}/${{ env.NGINX_IMAGE_NAME}}:${{ steps.version.outputs.client_version }}
          cache-to: type=inline
      - name: Generate artifact attestation
        continue-on-error: true
        if: ${{ needs.check-nginx-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') || contains(github.event.head_commit.message, '[skip nginx cache]') }}
        uses: actions/attest-build-provenance@v1
        with:
          subject-name: ${{ env.REGISTRY }}/${{ env.NGINX_IMAGE_NAME}}
          subject-digest: ${{ steps.push.outputs.digest }}
          push-to-registry: true

9. Обновляем локальный сборщик Docker-образов

Обновляем файл .docker/build-images.sh

#!/bin/bash
set -e

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${BUILDER_IMAGE_NAME}:latest" -f ./.docker/builder.Dockerfile . --progress=plain

# We build all applications
docker run --network host -v ./dist:/usr/src/app/dist -v ./apps:/usr/src/app/apps -v ./libs:/usr/src/app/libs ${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION}

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:latest" -f ./.docker/base-server.Dockerfile . --progress=plain

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${SERVER_IMAGE_NAME}:${SERVER_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${SERVER_IMAGE_NAME}:${SERVER_VERSION}" -t "${REGISTRY}/${SERVER_IMAGE_NAME}:latest" -f ./.docker/server.Dockerfile . --progress=plain --build-arg=\"BASE_SERVER_IMAGE_TAG=${ROOT_VERSION}\"

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:latest" -f ./.docker/migrations.Dockerfile . --progress=plain

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${NGINX_IMAGE_NAME}:${CLIENT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${NGINX_IMAGE_NAME}:${CLIENT_VERSION}" -t "${REGISTRY}/${NGINX_IMAGE_NAME}:latest" -f ./.docker/nginx.Dockerfile . --progress=plain

# We check the existence of a local image with the specified tag, if it does not exist, we start building the image
export IMG=${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build --network host -t "${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:latest" -f ./.docker/e2e-tests.Dockerfile . --progress=plain

10. Обновляем конфигурацию для локального запуска «Docker Compose» режима

Обновляем файл .docker/docker-compose-full.yml

version: '3'
networks:
  nestjs-mod-fullstack-network:
    driver: 'bridge'
services:
  nestjs-mod-fullstack-postgre-sql:
    image: 'bitnami/postgresql:15.5.0'
    container_name: 'nestjs-mod-fullstack-postgre-sql'
    networks:
      - 'nestjs-mod-fullstack-network'
    healthcheck:
      test:
        - 'CMD-SHELL'
        - 'pg_isready -U postgres'
      interval: '5s'
      timeout: '5s'
      retries: 5
    tty: true
    restart: 'always'
    environment:
      POSTGRESQL_USERNAME: '${SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME}'
      POSTGRESQL_PASSWORD: '${SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD}'
      POSTGRESQL_DATABASE: '${SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE}'
    volumes:
      - 'nestjs-mod-fullstack-postgre-sql-volume:/bitnami/postgresql'
  nestjs-mod-fullstack-postgre-sql-migrations:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-migrations:${ROOT_VERSION}'
    container_name: 'nestjs-mod-fullstack-postgre-sql-migrations'
    networks:
      - 'nestjs-mod-fullstack-network'
    tty: true
    environment:
      NX_SKIP_NX_CACHE: 'true'
      SERVER_ROOT_DATABASE_URL: '${SERVER_ROOT_DATABASE_URL}'
      SERVER_APP_DATABASE_URL: '${SERVER_APP_DATABASE_URL}'
    depends_on:
      nestjs-mod-fullstack-postgre-sql:
        condition: 'service_healthy'
    working_dir: '/usr/src/app'
    volumes:
      - './../apps:/usr/src/app/apps'
      - './../libs:/usr/src/app/libs'
  nestjs-mod-fullstack-server:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-server:${SERVER_VERSION}'
    container_name: 'nestjs-mod-fullstack-server'
    networks:
      - 'nestjs-mod-fullstack-network'
    healthcheck:
      test: ['CMD-SHELL', 'npx -y wait-on --timeout= --interval=1000 --window --verbose --log http://localhost:${SERVER_PORT}/api/health']
      interval: 30s
      timeout: 10s
      retries: 10
    tty: true
    environment:
      SERVER_APP_DATABASE_URL: '${SERVER_APP_DATABASE_URL}'
      SERVER_PORT: '${SERVER_PORT}'
    restart: 'always'
    depends_on:
      nestjs-mod-fullstack-postgre-sql:
        condition: service_healthy
      nestjs-mod-fullstack-postgre-sql-migrations:
        condition: service_completed_successfully
  nestjs-mod-fullstack-nginx:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx:${CLIENT_VERSION}'
    container_name: 'nestjs-mod-fullstack-nginx'
    networks:
      - 'nestjs-mod-fullstack-network'
    healthcheck:
      test: ['CMD-SHELL', 'curl -so /dev/null http://localhost:${NGINX_PORT} || exit 1']
      interval: 30s
      timeout: 10s
      retries: 10
    environment:
      SERVER_PORT: '${SERVER_PORT}'
      NGINX_PORT: '${NGINX_PORT}'
    restart: 'always'
    depends_on:
      nestjs-mod-fullstack-server:
        condition: service_healthy
    ports:
      - '${NGINX_PORT}:${NGINX_PORT}'
  nestjs-mod-fullstack-e2e-tests:
    image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-e2e-tests:${ROOT_VERSION}'
    container_name: 'nestjs-mod-fullstack-e2e-tests'
    networks:
      - 'nestjs-mod-fullstack-network'
    environment:
      BASE_URL: 'http://nestjs-mod-fullstack-nginx:${NGINX_PORT}'
    depends_on:
      nestjs-mod-fullstack-nginx:
        condition: service_healthy
    working_dir: '/usr/src/app'
    volumes:
      - './../apps:/usr/src/app/apps'
      - './../libs:/usr/src/app/libs'
  nestjs-mod-fullstack-https-portal:
    image: steveltn/https-portal:1
    container_name: 'nestjs-mod-fullstack-https-portal'
    networks:
      - 'nestjs-mod-fullstack-network'
    ports:
      - '80:80'
      - '443:443'
    links:
      - nestjs-mod-fullstack-nginx
    restart: always
    environment:
      STAGE: '${HTTPS_PORTAL_STAGE}'
      DOMAINS: '${SERVER_DOMAIN} -> http://nestjs-mod-fullstack-nginx:${NGINX_PORT}'
    depends_on:
      nestjs-mod-fullstack-nginx:
        condition: service_healthy
    volumes:
      - nestjs-mod-fullstack-https-portal-volume:/var/lib/https-portal
volumes:
  nestjs-mod-fullstack-postgre-sql-volume:
    name: 'nestjs-mod-fullstack-postgre-sql-volume'
  nestjs-mod-fullstack-https-portal-volume:
    name: 'nestjs-mod-fullstack-https-portal-volume'

11. Запускаем локальный «Docker Compose» режим и ждем успешного прохождения тестов

Когда мы изменяем много файлов или изменяем пармаметры девопс или устанавливаем новые зависимости, то нам необходимо локально убедится что все работает в режиме "Docker Compose", так как процесс сборки в CI/CD тратит бесплатные лимиты в случаи использования публичных раннеров, а также нагружает и удлиняет процесс деплоя при использовании собственных маломощных раннеров.

Локальный запуск в режиме "Docker Compose" также позволяет выявить проблемы которые могут появится при запуске через Kubernetes, так как сборка Docker-образов происходит почти одинаково.

При локальном запуске мы можем скачать и подключить Docker-образа которые использовались в Kubernetes, это помогает при поиске багов которые не повторяются на наших машинах и на наших локально собранных Docker-образах.

Команды

npm run docker-compose-full:prod:start
docker logs nestjs-mod-fullstack-e2e-tests

Вывод консоли

$ docker logs nestjs-mod-fullstack-e2e-tests

> @nestjs-mod-fullstack/source@0.0.0 test:e2e
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes

NX  Falling back to ts-node for local typescript execution. This may be a little slower.
 - To fix this, ensure @swc-node/register and @swc/core have been installed

 NX   Running target e2e for 2 projects:

- client-e2e
- server-e2e



> nx run client-e2e:e2e

> playwright test


Running 6 tests using 3 workers
  6 passed (4.9s)

To open last HTML report run:

  npx playwright show-report ../../dist/.playwright/apps/client-e2e/playwright-report


> nx run server-e2e:e2e

Setting up...
 PASS   server-e2e  apps/server-e2e/src/server/server.spec.ts
  GET /api
    ✓ should return a message (32 ms)
    ✓ should create and return a demo object (38 ms)
    ✓ should get demo object by id (9 ms)
    ✓ should get all demo object (7 ms)
    ✓ should delete demo object by id (8 ms)
    ✓ should get all demo object (6 ms)
Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.789 s
Ran all test suites.
Tearing down...



 NX   Successfully ran target e2e for 2 projects

12. Заменяем проверку наличия метки release в комментарии коммита на проверку наличия метки skip release

В предыдущем посте я добавлял метку [release] по которой мы принимали решение о необходимости запуска создания релиза, это было больше как пример, в реальности эту метку всегда забывают написать и приходится делать лишний не важный коммит для форсирования создания релиза.

Для того чтобы релиз всегда пробовал запустится заменим метку [release] на [skip release] и поменяем логику работы, теперь если встречаем указанную метку мы пропускаем шаг создания релиза.

Обновляем файл .github/workflows/kubernetes.yml

name: 'Kubernetes'

on:
  push:
    branches: ['master']
env:
  REGISTRY: ghcr.io
  BASE_SERVER_IMAGE_NAME: ${{ github.repository }}-base-server
  BUILDER_IMAGE_NAME: ${{ github.repository }}-builder
  MIGRATIONS_IMAGE_NAME: ${{ github.repository }}-migrations
  SERVER_IMAGE_NAME: ${{ github.repository }}-server
  NGINX_IMAGE_NAME: ${{ github.repository }}-nginx
  E2E_TESTS_IMAGE_NAME: ${{ github.repository }}-e2e-tests
  COMPOSE_INTERACTIVE_NO_CLI: 1
  NX_DAEMON: false
  NX_PARALLEL: false
  NX_SKIP_NX_CACHE: true
  DISABLE_SERVE_STATIC: true
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write # to be able to publish a GitHub release
      issues: write # to be able to comment on released issues
      pull-requests: write # to be able to comment on released pull requests
      id-token: write # to enable use of OIDC for npm provenance
    steps:
      - uses: actions/checkout@v4
        if: ${{ !contains(github.event.head_commit.message, '[skip release]') }}
      - run: npm install --prefer-offline --no-audit --progress=false
        if: ${{ !contains(github.event.head_commit.message, '[skip release]') }}
      - run: npm run nx -- run-many --target=semantic-release --all --parallel=false
        if: ${{ !contains(github.event.head_commit.message, '[skip release]') }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ...

13. Добавляем строгости коду

Помимо настроек lint-staged для приведения кода к общему стилю, необходимо также иметь и общие параметры eslint и typescript-compilerOptions с дополнительными правилами строгости кода.

Обычно я не трогаю стандартные настройки eslint И prettier, просто добавляю немного строгости в корневой Typescript-конфиг.

Добавляем дополнительные правила в tsconfig.base.json

{
  // ...
  "compilerOptions": {
    // ...
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": true,
    "noImplicitOverride": true,
    "strictPropertyInitialization": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "esModuleInterop": true,
    "noImplicitAny": false
    // ...
  }
  // ...
}

Запускаем npm run manual:prepare и чиним все что сломалось и перезапускаем повторно до тех пор пока все ошибки не исправим.

14. Коммитим код и ждем успешного создания релизов и прохождения тестов

Текущий результат работы CI/CD: https://github.com/nestjs-mod/nestjs-mod-fullstack/actions/runs/10904254598
Текущий сайт: https://fullstack.nestjs-mod.com

Заключение

Если в проекте имеются другие файлы которые могут меняться в зависимости от настроек среды разработки, эти файлы также нужно указать в правилах lint-staged.

Строгость тоже можно еще сильнее сделать как и правила eslint, но каждый раз нужно замерять время работы, так например правило eslint для сортировки импортов запускает парсер ast-представления, в большом проекте это просто очень долго работает.

В этом посте я показал как можно ускорить деплой за счет версионирования фронтенда, таким же образом можно поступать и с микросервисами.

Планы

Так как основные моменты по девопс мне удалось завершить, то в следующих постах уже будут краткие описания разработки основных фич которые я планировал сделать.

В следующем посте я создам вебхук-модуль на NestJS для предоставления оповещений о наших событиях сторонним сервисам…

Ссылки

https://nestjs.com — официальный сайт фреймворка
https://nestjs-mod.com — официальный сайт дополнительных утилит
https://fullstack.nestjs-mod.com — сайт из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack — проект из поста
https://github.com/nestjs-mod/nestjs-mod-fullstack/compare/2f9b6eddb32a9777fabda81afa92d9aaebd432cc…460257364bb4ce8e23fe761fbc9ca7462bc89b61 — изменения

© Habrahabr.ru