diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..3ed2135 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,68 @@ +views: + - changed-files: + - any-glob-to-any-file: + - 'src/pages/**/*' + - 'src/widgets/**/*' + - 'src/app/layouts/**/*' + +features: + - changed-files: + - any-glob-to-any-file: + - 'src/features/**/*' + +domain: + - changed-files: + - any-glob-to-any-file: + - 'src/entities/**/*' + +ui-kit: + - changed-files: + - any-glob-to-any-file: + - 'src/shared/ui/**/*' + - 'src/app/styles/**/*' + - '.storybook/**/*' + - '**/*.{css,scss}' + +core-logic: + - changed-files: + - any-glob-to-any-file: + - 'src/shared/{api,lib,config,providers}/**/*' + - 'src/app/providers/**/*' + +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}' + +config: + - changed-files: + - any-glob-to-any-file: + - '*.config.*' + - 'tsconfig.json' + - '.env*' + - '.eslintrc*' + - '.prettier*' + - all-globs-to-all-files: + - '!package.json' + +documentation: + - changed-files: + - any-glob-to-any-file: + - '**/*.md' + - 'LICENSE' \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0803c8c..83b9c6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,21 @@ 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: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -14,6 +26,10 @@ jobs: permissions: contents: read packages: write + 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' }} steps: - uses: actions/checkout@v4 @@ -26,18 +42,12 @@ jobs: run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - 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 }} @@ -67,7 +77,8 @@ jobs: NEXT_PUBLIC_FARO_APP_NAME=${{ vars.NEXT_PUBLIC_FARO_APP_NAME }} NEXT_PUBLIC_FARO_APP_NAMESPACE=${{ vars.NEXT_PUBLIC_FARO_APP_NAMESPACE }} SKIP_ENV_VALIDATION=true - 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 418e50d..9af3b3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,39 @@ name: CI workflow on: + push: + branches: + - main + - dev + - "feat/**" + - "fix/**" + - "refactor/**" + - "chore/**" + - "perf/**" + - "build/**" + - "ci/**" + paths-ignore: + - "**.md" + - "infra/**" + - ".gitignore" + - "docker-compose.yml" + pull_request: - branches: [ "dev", "main" ] + branches: + - main + - dev paths-ignore: - - "*.md" + - "**.md" + - "infra/**" + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: CI: runs-on: ubuntu-latest @@ -24,7 +48,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 24 cache: "pnpm" - name: Install deps diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 0000000..d16013c --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,45 @@ +name: "Cleanup" + +on: + schedule: + - cron: "0 0 * * 0" + workflow_dispatch: + +permissions: + actions: write + contents: read + +jobs: + garbage-collector: + name: "Purge Storage" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: "Clean Actions Cache" + shell: bash + run: | + 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: "Clean Old Artifacts" + shell: bash + run: | + echo "::group::Deleting Artifacts" + artifacts=$(gh api repos/${{ github.repository }}/actions/artifacts --paginate -q '.artifacts[].id' || echo "") + + 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/codeql.yml b/.github/workflows/codeql.yml index 9b0d4e0..36fc378 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,17 +1,27 @@ -name: 'CodeQL' +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' + - 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/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..3fa3d4b --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,43 @@ +name: "Pull Request Labeler" + +on: + # Важно: target позволяет работать в PR из форков + # pull_request_target: + pull_request: + 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, entities and migrations" + "ui-kit:ffcc00:Shared UI components, styles and storybook" + "core-logic:e99695:Global providers, api instances and core shared libs" + "features:00c5ff:User scenarios and sliced features" + "domain:fbca04:Business entities and models" + "views:a2eeef:Pages, widgets and layouts" + "testing:ff69b4:Unit, integration and E2E tests" + "devops:006b75:Infrastructure, Docker and CI/CD pipelines" + "dx:eeeeee:Tooling, linting and workspace configs" + "documentation:0075ca:Documentation and markdown files" + "dependencies:0366d6:Dependency updates (package.json, pnpm-lock)" + ) + + 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 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 9843e92..41f37c0 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -4,15 +4,74 @@ 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: | + type=semver,pattern={{version}},value=${{ needs.release-please.outputs.tag_name }} + type=semver,pattern={{major}},value=${{ needs.release-please.outputs.tag_name }} + type=raw,value=latest + + - name: Build and push Production Image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.prod + build-args: | + NEXT_PUBLIC_API_BASE_URL=${{ vars.NEXT_PUBLIC_API_BASE_URL }} + NEXT_PUBLIC_FARO_URL=${{ vars.NEXT_PUBLIC_FARO_URL }} + NEXT_PUBLIC_FARO_APP_VERSION=${{ vars.NEXT_PUBLIC_FARO_APP_VERSION }} + NEXT_PUBLIC_APP_ENV=${{ vars.NEXT_PUBLIC_APP_ENV }} + NEXT_PUBLIC_FARO_APP_NAME=${{ vars.NEXT_PUBLIC_FARO_APP_NAME }} + NEXT_PUBLIC_FARO_APP_NAMESPACE=${{ vars.NEXT_PUBLIC_FARO_APP_NAMESPACE }} + SKIP_ENV_VALIDATION=true + 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 7e54907..8aff0f8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,8 +1,9 @@ -name: 'Close stale issues and PRs' +name: "Close stale issues and PRs" on: schedule: - - cron: '30 1 * * *' + - cron: "30 1 * * *" + workflow_dispatch: jobs: stale: @@ -10,12 +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