diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..72abfdc27 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,61 @@ +# Auto-labeling configuration for Pull Requests +# Based on file paths changed in the PR +# See: https://github.com/actions/labeler +# Note: actions/labeler@v5 requires changed-files format + +# Bug fix labels - matches files with fix/bug/error in name +fix: + - changed-files: + any-glob-to-any-file: ['**/*fix*', '**/*bug*', '**/*error*'] + +# Documentation labels - matches markdown files and docs directories +docs: + - changed-files: + any-glob-to-any-file: ['**/*.md', 'docs/**', 'documentation/**'] + +# Test labels - matches test files and directories +test: + - changed-files: + any-glob-to-any-file: ['**/__tests__/**', '**/*.test.*', '**/*.spec.*', '**/test/**'] + +# Chore/maintenance labels - matches config files and dependency files +chore: + - changed-files: + any-glob-to-any-file: ['.github/**', '**/package.json', '**/package-lock.json', '**/go.mod', '**/go.sum', '**/.gitignore', '**/.editorconfig'] + +# CI/CD labels - matches workflow files +ci: + - changed-files: + any-glob-to-any-file: ['.github/workflows/**', '.github/actions/**'] + +# Build system labels - matches build-related files +build: + - changed-files: + any-glob-to-any-file: ['**/Dockerfile*', '**/docker-compose*', '**/Makefile', '**/build.*', '**/CMakeLists.txt'] + +# Style/formatting labels - matches formatting config files +style: + - changed-files: + any-glob-to-any-file: ['**/.prettierrc*', '**/.eslintrc*', '**/prettier.config.*', '**/eslint.config.*'] + +# Component-specific labels +formulus: + - changed-files: + any-glob-to-any-file: ['formulus/**'] + +formulus-formplayer: + - changed-files: + any-glob-to-any-file: ['formulus-formplayer/**'] + +synkronus: + - changed-files: + any-glob-to-any-file: ['synkronus/**'] + +synkronus-cli: + - changed-files: + any-glob-to-any-file: ['synkronus-cli/**'] + +# Dependencies labels - matches dependency files +dependencies: + - changed-files: + any-glob-to-any-file: ['**/package.json', '**/package-lock.json', '**/go.mod', '**/go.sum', '**/yarn.lock', '**/pnpm-lock.yaml'] diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml new file mode 100644 index 000000000..09081c7d3 --- /dev/null +++ b/.github/workflows/auto-label.yml @@ -0,0 +1,27 @@ +name: Auto Label PRs + +on: + pull_request: + types: [opened, edited, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +jobs: + label: + name: Auto Label PR + runs-on: ubuntu-latest + continue-on-error: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Label PR based on changed files + uses: actions/labeler@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler.yml + sync-labels: true + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..ad81502de --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,256 @@ +name: CI + +on: + pull_request: + branches: + - main + - develop + push: + branches: + - main + - develop + paths-ignore: + - '**.md' + - '.gitignore' + - 'LICENSE' + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE.md' + - '.github/labeler.yml' + workflow_dispatch: + +# Cancel in-progress runs for the same workflow and branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: '20' + GO_VERSION: '1.24' + +jobs: + # Detect which components have changed + changes: + name: Detect Changes + runs-on: ubuntu-latest + outputs: + formulus: ${{ steps.filter.outputs.formulus }} + formulus-formplayer: ${{ steps.filter.outputs.formulus-formplayer }} + synkronus: ${{ steps.filter.outputs.synkronus }} + synkronus-cli: ${{ steps.filter.outputs.synkronus-cli }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed components + uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + formulus: + - 'formulus/**' + formulus-formplayer: + - 'formulus-formplayer/**' + synkronus: + - 'synkronus/**' + synkronus-cli: + - 'synkronus-cli/**' + + # Lint and test for formulus (React Native) + formulus: + name: Formulus (React Native) + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.formulus == 'true' || github.event_name == 'workflow_dispatch' }} + + defaults: + run: + working-directory: ./formulus + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: formulus/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + continue-on-error: false + + - name: Run tests + run: npm test -- --coverage --watchAll=false + continue-on-error: false + + # Lint and test for formulus-formplayer (React Web) + formulus-formplayer: + name: Formulus Formplayer (React Web) + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.formulus-formplayer == 'true' || github.event_name == 'workflow_dispatch' }} + + defaults: + run: + working-directory: ./formulus-formplayer + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + cache-dependency-path: formulus-formplayer/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test -- --coverage --watchAll=false + continue-on-error: false + + - name: Build + run: npm run build + continue-on-error: false + + # Build and test for synkronus (Go backend) + synkronus: + name: Synkronus (Go Backend) + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.synkronus == 'true' || github.event_name == 'workflow_dispatch' }} + + defaults: + run: + working-directory: ./synkronus + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: synkronus/go.sum + + - name: Verify dependencies + run: go mod verify + + - name: Download dependencies + run: go mod download + + - name: Run gofmt + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "Code is not formatted. Run 'go fmt ./...'" + gofmt -s -d . + exit 1 + fi + + - name: Run golangci-lint (if available) + run: | + if command -v golangci-lint &> /dev/null; then + golangci-lint run + else + echo "golangci-lint not installed, skipping..." + fi + continue-on-error: true + + - name: Run tests + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Build + run: go build -v ./cmd/synkronus + continue-on-error: false + + # Build and test for synkronus-cli (Go CLI) + synkronus-cli: + name: Synkronus CLI (Go CLI) + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.synkronus-cli == 'true' || github.event_name == 'workflow_dispatch' }} + + defaults: + run: + working-directory: ./synkronus-cli + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache-dependency-path: synkronus-cli/go.sum + + - name: Verify dependencies + run: go mod verify + + - name: Download dependencies + run: go mod download + + - name: Run gofmt + run: | + if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then + echo "Code is not formatted. Run 'go fmt ./...'" + gofmt -s -d . + exit 1 + fi + + - name: Run tests + run: go test -v -race -coverprofile=coverage.out ./... + + - name: Build + run: go build -v ./cmd/synkronus + continue-on-error: false + + # Summary job that runs after all component jobs + ci-summary: + name: CI Summary + runs-on: ubuntu-latest + needs: [formulus, formulus-formplayer, synkronus, synkronus-cli] + if: always() + + steps: + - name: Check job results + run: | + echo "## CI Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Component | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Formulus | ${{ needs.formulus.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Formulus Formplayer | ${{ needs.formulus-formplayer.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Synkronus | ${{ needs.synkronus.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Synkronus CLI | ${{ needs.synkronus-cli.result }} |" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.formulus.result }}" != "success" ] && [ "${{ needs.formulus.result }}" != "skipped" ]; then + echo "❌ Formulus CI failed" + exit 1 + fi + if [ "${{ needs.formulus-formplayer.result }}" != "success" ] && [ "${{ needs.formulus-formplayer.result }}" != "skipped" ]; then + echo "❌ Formulus Formplayer CI failed" + exit 1 + fi + if [ "${{ needs.synkronus.result }}" != "success" ] && [ "${{ needs.synkronus.result }}" != "skipped" ]; then + echo "❌ Synkronus CI failed" + exit 1 + fi + if [ "${{ needs.synkronus-cli.result }}" != "success" ] && [ "${{ needs.synkronus-cli.result }}" != "skipped" ]; then + echo "❌ Synkronus CLI CI failed" + exit 1 + fi + + echo "✅ All CI checks passed!" + diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 000000000..7f8c5840c --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,65 @@ +name: Validate PR Title + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: + pull-requests: write + contents: read + +jobs: + pr-title: + name: Validate PR Title Format + runs-on: ubuntu-latest + + steps: + - name: Check PR Title Format + uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Allowed commit types matching our Conventional Commits specification + types: | + feat + fix + docs + style + refactor + perf + test + build + ci + chore + revert + # Require scope (optional but recommended) + requireScope: false + # Allow breaking changes indicator + allowMergeCommits: false + # Custom validation pattern (optional) + # This enforces: (): + subjectPattern: ^.+$ + subjectPatternError: | + The subject must be a non-empty string. + See https://www.conventionalcommits.org/ for more information. + # Scopes that are allowed (optional - leave empty to allow any scope) + scopes: [] + # Wording for the PR title + # This will be used in the status check message + titleWording: | + The PR title must follow the Conventional Commits format: + `(): ` + + Examples: + - feat(auth): add OAuth2 login + - fix(ui): resolve button alignment issue + - docs: update installation guide + + See [CONTRIBUTING.md](CONTRIBUTING.md#commit-message-convention) for details. + # Wording for the PR title when it's invalid + titleWordingFail: | + The PR title does not follow the Conventional Commits format. + Please update the title to match: `(): ` + + See [CONTRIBUTING.md](CONTRIBUTING.md#commit-message-convention) for details. +