From 586f76ef047f77f56819faad80ba96fd7883ac47 Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 02:26:12 +0300 Subject: [PATCH 1/7] refactor(workflow): stabilize ci/cd pipelines with manual overrides and security checks --- .github/workflows/build.yml | 30 +++++++++++++--- .github/workflows/ci.yml | 33 ++++++++++++++--- .github/workflows/codeql.yml | 17 +++++++-- .github/workflows/release-please.yml | 54 ++++++++++++++++++++++++++++ .github/workflows/stale.yml | 22 ++++++++++-- 5 files changed, 144 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f1a92a4..67e2e37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,19 @@ name: Build and Push on: push: - branches: [dev, main, feat/**] + branches: [main, dev, 'feat/**', 'fix/**', 'refactor/**', 'chore/**'] + pull_request: + branches: [main, dev] + workflow_dispatch: + inputs: + force_push: + description: 'Force push image to registry?' + type: boolean + default: false + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: REGISTRY: ghcr.io @@ -11,17 +23,25 @@ env: jobs: build-push: runs-on: ubuntu-latest + env: + IS_BASE_BRANCH: ${{ github.ref_name == 'main' || github.ref_name == 'dev' }} + IS_PUSH: ${{ github.event_name == 'push' }} + FORCE_PUSH: ${{ github.event.inputs.force_push == 'true' }} + permissions: contents: read packages: write steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to the Container registry + if: ${{ (env.IS_PUSH == 'true' && env.IS_BASE_BRANCH == 'true') || + env.FORCE_PUSH == 'true' }} uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} @@ -36,14 +56,16 @@ jobs: tags: | type=ref,event=branch type=sha,format=short - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + # latest вешаем только когда мерджим в main + type=raw,value=latest,enable=${{ github.ref_name == 'main' }} - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile.prod - push: true + push: ${{ (env.IS_PUSH == 'true' && env.IS_BASE_BRANCH == 'true') || + env.FORCE_PUSH == 'true' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93a1c10..cd6d74e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,35 @@ name: CI on: - pull_request: - branches: [dev, main, 'feat/**'] push: - branches: [dev, main, 'feat/**'] + branches: + - main + - dev + - 'feat/**' + - 'fix/**' + - 'refactor/**' + - 'chore/**' + - 'perf/**' + - 'build/**' + - 'ci/**' + paths-ignore: + - '**.md' + - 'infra/**' + - '.gitignore' + - 'docker-compose.yml' + + pull_request: + branches: + - main + - dev + paths-ignore: + - '**.md' + - 'infra/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: quality-check: @@ -25,7 +50,7 @@ jobs: cache: 'pnpm' - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --prefer-offline - name: Run Lint run: pnpm run lint diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 129d47e..cc77c3f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,16 +2,26 @@ name: 'CodeQL' on: push: - branches: [main, dev, feat/**, chore/**, build/**] + branches: [main, dev] + paths-ignore: + - '**.md' + - 'infra/**' + - 'migrations/**' pull_request: - branches: [main] + branches: [main, dev] + paths-ignore: + - '**.md' schedule: - cron: '15 13 * * 5' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + jobs: analyze: name: Analyze runs-on: ubuntu-latest + timeout-minutes: 360 permissions: actions: read contents: read @@ -25,9 +35,12 @@ jobs: uses: github/codeql-action/init@v3 with: languages: javascript-typescript + queries: security-extended - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 + with: + category: '/language:javascript-typescript' diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 95b126c..c7cfee1 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -4,15 +4,69 @@ on: push: branches: - main + workflow_dispatch: permissions: contents: write pull-requests: write +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + jobs: release-please: runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} steps: - uses: googleapis/release-please-action@v4 + id: release with: release-type: node + + publish-release: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + # Версия из тега (например, 1.2.3) + type=semver,pattern={{version}},value=${{ needs.release-please.outputs.tag_name }} + # Мажорная версия (например, 1) + type=semver,pattern={{major}},value=${{ needs.release-please.outputs.tag_name }} + # Всегда обновляем latest для продакшена + type=raw,value=latest + + - name: Build and push Production Image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.prod + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f812282..fac0b76 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,6 +3,7 @@ name: 'Close stale issues and PRs' on: schedule: - cron: '30 1 * * *' + workflow_dispatch: jobs: stale: @@ -10,10 +11,27 @@ jobs: permissions: issues: write pull-requests: write + steps: - uses: actions/stale@v9 with: - stale-issue-message: 'Эта задача давно не обновлялась. Она будет закрыта через 5 дней, если не появится новой активности.' - stale-pr-message: 'Этот PR замер. Мы закроем его через 5 дней, чтобы не копить очередь, но вы всегда можете переоткрыть его позже.' + stale-issue-message: >- + Эта задача давно не обновлялась. Она будет закрыта через 5 + дней, если не появится новой активности. 🤖 + stale-pr-message: >- + Этот PR замер. Мы закроем его через 5 дней, чтобы не + копить очередь, но вы всегда можете переоткрыть его + позже. 🤖 + days-before-stale: 30 days-before-close: 5 + + exempt-issue-labels: 'bug,priority:high,pinned,security' + exempt-pr-labels: 'dependencies,security' + exempt-all-milestones: true + + stale-issue-label: 'stale' + stale-pr-label: 'stale' + + operations-per-run: 50 + ascending: true From b1ecb5be4dd2a580ff726cb32fd3e564e067946e Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 02:44:52 +0300 Subject: [PATCH 2/7] feat: db ci migration --- .github/workflows/migrations.yml | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/migrations.yml diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml new file mode 100644 index 0000000..93f3fc0 --- /dev/null +++ b/.github/workflows/migrations.yml @@ -0,0 +1,83 @@ +name: 'DB Integrity' + +on: + pull_request: + branches: [main, dev] + paths: + - 'src/shared/entities/**' + - 'migrations/**' + - 'drizzle.config.ts' + - 'package.json' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + DB_USER: test + DB_PASSWORD: password + DB_NAME: test + DB_URL: postgres://user:password@localhost:5432/test_db + +jobs: + check: + name: 'Check & Test' + runs-on: ubuntu-latest + timeout-minutes: 10 + + services: + postgres: + image: postgres:15-alpine + env: + POSTGRES_USER: ${{ env.DB_USER }} + POSTGRES_PASSWORD: ${{ env.DB_PASSWORD }} + POSTGRES_DB: ${{ env.DB_NAME }} + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready --health-interval 10s + --health-timeout 5s --health-retries 5 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'pnpm' + + - name: Install + run: | + echo "::group::pnpm install" + pnpm install --frozen-lockfile + echo "::endgroup::" + + - name: Drift Check + env: + DATABASE_URL: ${{ env.DB_URL }} + run: | + echo "::group::Drizzle Check" + pnpm drizzle-kit check + echo "::endgroup::" + + - name: Seed + run: | + echo "::group::Seed Data" + # pnpm ts-node ./scripts/seed-ci.ts + echo "Done" + echo "::endgroup::" + + - name: Migrate + env: + DATABASE_URL: ${{ env.DB_URL }} + run: | + echo "::group::Apply Schema" + pnpm drizzle-kit push --force + echo "::endgroup::" From a9704ae50f4f61480b2d5507641ba9a9f68d066d Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 03:06:27 +0300 Subject: [PATCH 3/7] ci: cleanup function cache data --- .github/workflows/cleanup.yml | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/cleanup.yml diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 0000000..a926fbe --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,43 @@ +name: 'Cleanup' + +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: + actions: write + contents: read + +jobs: + garbage-collector: + name: 'Cache & Artifacts' + runs-on: ubuntu-latest + steps: + - name: 'Purge All Caches' + shell: bash + run: | + gh extension install actions/gh-actions-cache + echo "::group::Cleaning Caches" + # Удаляем все кэши. Если нужно оставить main, добавь: -B main + gh actions-cache delete --all --confirm + echo "::endgroup::" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: 'Purge Old Artifacts' + shell: bash + run: | + echo "::group::Cleaning Artifacts" + gh run allocation-status --repo ${{ github.repository }} + + artifacts=$(gh api repos/${{ github.repository }}/actions/artifacts --paginate -q '.artifacts[].id') + + for artifact_id in $artifacts; do + gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id + done + + echo "All artifacts deleted." + echo "::endgroup::" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6bb8ae3616bc88c346d3a7a87f21854f8dda6cbf Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 03:11:14 +0300 Subject: [PATCH 4/7] ci: refactor cleanup workflow and optimize db integrity envs --- .github/workflows/cleanup.yml | 34 +++++++++++++++++--------------- .github/workflows/migrations.yml | 6 +++--- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index a926fbe..407b8a9 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -11,33 +11,35 @@ permissions: jobs: garbage-collector: - name: 'Cache & Artifacts' + name: 'Purge Storage' runs-on: ubuntu-latest steps: - - name: 'Purge All Caches' + - name: Checkout + uses: actions/checkout@v4 + + - name: 'Clean Actions Cache' shell: bash run: | - gh extension install actions/gh-actions-cache - echo "::group::Cleaning Caches" - # Удаляем все кэши. Если нужно оставить main, добавь: -B main - gh actions-cache delete --all --confirm + echo "::group::Deleting Caches" + gh cache delete --all --succeed-on-no-caches || echo "Caches already empty or cleared" echo "::endgroup::" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: 'Purge Old Artifacts' + - name: 'Clean Old Artifacts' shell: bash run: | - echo "::group::Cleaning Artifacts" - gh run allocation-status --repo ${{ github.repository }} - - artifacts=$(gh api repos/${{ github.repository }}/actions/artifacts --paginate -q '.artifacts[].id') - - for artifact_id in $artifacts; do - gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$artifact_id - done + echo "::group::Deleting Artifacts" + artifacts=$(gh api repos/${{ github.repository }}/actions/artifacts --paginate -q '.artifacts[].id' || echo "") - echo "All artifacts deleted." + if [ -n "$artifacts" ]; then + for id in $artifacts; do + gh api -X DELETE repos/${{ github.repository }}/actions/artifacts/$id || true + done + echo "Artifacts cleared." + else + echo "No artifacts found." + fi echo "::endgroup::" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 93f3fc0..c631217 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -16,10 +16,10 @@ concurrency: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - DB_USER: test - DB_PASSWORD: password + DB_USER: tracker + DB_PASSWORD: super-tracker-password DB_NAME: test - DB_URL: postgres://user:password@localhost:5432/test_db + DB_URL: postgres://tracker:super-tracker-password@localhost:5432/test jobs: check: From 6238352c166e9ec751870915864b3c55b4864ba1 Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 03:37:48 +0300 Subject: [PATCH 5/7] ci: add auto-labeler to pr ci: test ci: test --- .github/labeler.yml | 56 +++++++++++++++++++++++++++++++++++ .github/workflows/labeler.yml | 38 ++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..35ee000 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,56 @@ +core: + - changed-files: + - any-glob-to-any-file: 'src/**/*' + - all-globs-to-all-files: + - '!src/shared/entities/**/*' + - '!src/**/*.spec.{ts,js}' + +database: + - changed-files: + - any-glob-to-any-file: + - 'src/shared/entities/**/*' + - 'libs/database/**/*' + - 'migrations/**/*' + - 'drizzle.config.ts' + +dependencies: + - changed-files: + - any-glob-to-any-file: + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + +devops: + - changed-files: + - any-glob-to-any-file: + - 'infra/**/*' + - '.github/workflows/**/*' + - 'Dockerfile*' + - '.dockerignore' + +testing: + - changed-files: + - any-glob-to-any-file: + - 'test/**/*' + - 'src/**/*.spec.{ts,js}' + - 'k6/**/*' + - 'vitest.config*' + +libs: + - changed-files: + - any-glob-to-any-file: 'libs/**/*' + - all-globs-to-all-files: + - '!libs/database/**/*' + +dx: + - changed-files: + - any-glob-to-any-file: + - 'pnpm-workspace.yaml' + - '.*' + - '!package.json' + +documentation: + - changed-files: + - any-glob-to-any-file: + - '**/*.md' + - 'LICENSE' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..7b94cf6 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,38 @@ +name: 'Pull Request Labeler' + +on: + # Важно: target позволяет работать в PR из форков + pull_request_target: + types: [opened, synchronize] + +jobs: + label: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Ensure Labels Exist + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + labels=( + "database:5319e7:Database schema and migrations" + "core:e99695:Main application logic" + "testing:ff69b4:Unit and E2E tests" + "devops:006b75:Infrastructure and CI/CD" + "shared-libs:bfdadc:Shared libraries in libs/" + "dx:eeeeee:Developer experience and configs" + "documentation:0075ca:Documentation and markdown files" + ) + + for label in "${labels[@]}"; do + IFS=":" read -r name color desc <<< "$label" + gh label create "$name" --color "$color" --description "$desc" --repo ${{ github.repository }} || true + done + + - name: Run Labeler + uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml + sync-labels: true From 4f6c79e0fb532e42f1dbf4938cf2720b5057ae44 Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 15:00:35 +0300 Subject: [PATCH 6/7] ci: rename issue template --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c90da45..f356707 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,4 +1,4 @@ -name: '🚀 Feature Request' +name: 'Feature Request' description: 'Предложить новую идею или улучшение' labels: ['enhancement'] body: From 143da3c86c9c5bbfcced0de3fc32568bcd7c61f1 Mon Sep 17 00:00:00 2001 From: soorq Date: Tue, 12 May 2026 15:07:32 +0300 Subject: [PATCH 7/7] ci: resolve all to env FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and formating --- .github/workflows/build.yml | 1 + .github/workflows/ci.yml | 5 ++++- .github/workflows/migrations.yml | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67e2e37..fdc2afc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ concurrency: cancel-in-progress: true env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd6d74e..763a6e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: quality-check: name: Lint & Test @@ -46,7 +49,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index c631217..4847ac5 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -1,4 +1,4 @@ -name: 'DB Integrity' +name: 'Database Consistency Check' on: pull_request: @@ -37,8 +37,10 @@ jobs: ports: - 5432:5432 options: >- - --health-cmd pg_isready --health-interval 10s - --health-timeout 5s --health-retries 5 + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout