feat: shared-schemas #58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Lint PR Changes | |
| on: | |
| pull_request: | |
| types: [opened, reopened] | |
| issue_comment: | |
| types: [created] | |
| jobs: | |
| lint: | |
| # Run on PR events or when the comment is "/linter" on a PR | |
| if: | | |
| github.event_name == 'pull_request' || | |
| (github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| contains(github.event.comment.body, '/linter')) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Get PR details | |
| id: pr | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| let prNumber, prRef; | |
| if (context.eventName === 'pull_request') { | |
| prNumber = context.payload.pull_request.number; | |
| prRef = context.payload.pull_request.head.ref; | |
| } else if (context.eventName === 'issue_comment') { | |
| prNumber = context.payload.issue.number; | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber | |
| }); | |
| prRef = pr.head.ref; | |
| } | |
| core.setOutput('number', prNumber); | |
| core.setOutput('ref', prRef); | |
| return { prNumber, prRef }; | |
| - name: React to comment (if triggered by comment) | |
| if: github.event_name == 'issue_comment' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.reactions.createForIssueComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: context.payload.comment.id, | |
| content: 'rocket' | |
| }); | |
| - name: Checkout PR | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.pr.outputs.ref }} | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '18' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 9.0.0 | |
| run_install: false | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Cache Prisma engines | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/prisma | |
| packages/prisma-db/generated | |
| key: ${{ runner.os }}-prisma-${{ hashFiles('packages/prisma-db/schema.prisma') }} | |
| restore-keys: | | |
| ${{ runner.os }}-prisma- | |
| - name: Cache built packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| packages/**/dist | |
| packages/**/.turbo | |
| key: ${{ runner.os }}-packages-build-${{ hashFiles('packages/**/package.json', 'packages/**/tsconfig.json', 'packages/**/*.ts') }} | |
| restore-keys: | | |
| ${{ runner.os }}-packages-build- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate Prisma Client | |
| run: | | |
| cd packages/prisma-db | |
| # Retry up to 3 times if Prisma's binary server fails | |
| for i in 1 2 3; do | |
| if pnpm exec prisma generate; then | |
| echo "Prisma client generated successfully" | |
| break | |
| else | |
| if [ $i -eq 3 ]; then | |
| echo "Failed to generate Prisma client after 3 attempts" | |
| exit 1 | |
| fi | |
| echo "Attempt $i failed, retrying in 5 seconds..." | |
| sleep 5 | |
| fi | |
| done | |
| - name: Build packages | |
| run: | | |
| # Build only the packages (not the apps) | |
| # This generates TypeScript types for @repo/utils-core and other packages | |
| pnpm --filter "./packages/**" build || echo "Some packages don't have build scripts, continuing..." | |
| - name: Get changed files | |
| id: changed-files | |
| run: | | |
| # Fetch the base branch | |
| git fetch origin ${{ github.base_ref || 'main' }}:${{ github.base_ref || 'main' }} | |
| # Get list of changed files (excluding deleted files) | |
| CHANGED_FILES=$(git diff --name-only --diff-filter=d origin/${{ github.base_ref || 'main' }}...HEAD) | |
| # Filter to only lintable files (ts, tsx, js, jsx, mjs, cjs) | |
| LINTABLE_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(ts|tsx|js|jsx|mjs|cjs)$' || true) | |
| # Remove empty lines, node_modules, and commonly ignored files | |
| LINTABLE_FILES=$(echo "$LINTABLE_FILES" | grep -v 'node_modules' | grep -v '^$' | grep -v 'eslint.config' | grep -v '.eslintrc' || true) | |
| if [ -z "$LINTABLE_FILES" ]; then | |
| echo "No lintable files changed" | |
| echo "has_files=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Changed lintable files:" | |
| echo "$LINTABLE_FILES" | |
| echo "has_files=true" >> $GITHUB_OUTPUT | |
| # Save files to a temporary file for later use | |
| echo "$LINTABLE_FILES" > /tmp/changed_files.txt | |
| fi | |
| - name: Run ESLint on changed files | |
| if: steps.changed-files.outputs.has_files == 'true' | |
| id: eslint | |
| run: | | |
| set +e # Don't exit on error, we want to capture the output | |
| # Read changed files | |
| CHANGED_FILES=$(cat /tmp/changed_files.txt) | |
| # Dynamically discover workspaces using pnpm | |
| echo "Discovering workspaces..." | |
| WORKSPACES=$(pnpm list -r --depth -1 --parseable 2>/dev/null | grep -v "^$(pwd)$" || true) | |
| if [ -z "$WORKSPACES" ]; then | |
| echo "No workspaces found" | |
| exit 0 | |
| fi | |
| echo "Found workspaces:" | |
| echo "$WORKSPACES" | |
| LINT_EXIT_CODE=0 | |
| LINT_OUTPUT="" | |
| FAILED_WORKSPACES="" | |
| REPO_ROOT=$(pwd) | |
| # Process each workspace - continue even if one fails | |
| while IFS= read -r workspace_path; do | |
| workspace_name=$(basename "$workspace_path") | |
| # Get relative path from repo root | |
| workspace_relative_path=$(python3 -c "import os.path; print(os.path.relpath('$workspace_path', '$REPO_ROOT'))") | |
| # Find files in this workspace | |
| workspace_files=$(echo "$CHANGED_FILES" | grep "^$workspace_relative_path/" || true) | |
| if [ -n "$workspace_files" ]; then | |
| echo "::group::Linting $workspace_name ($workspace_relative_path)" | |
| cd "$workspace_path" | |
| RELATIVE_FILES="" | |
| for file in $workspace_files; do | |
| RELATIVE_FILE=$(echo "$file" | sed "s|^$workspace_relative_path/||") | |
| RELATIVE_FILES="$RELATIVE_FILES $RELATIVE_FILE" | |
| done | |
| echo "Files: $RELATIVE_FILES" | |
| # Run eslint and capture output - don't stop on error | |
| WORKSPACE_EXIT=0 | |
| WORKSPACE_OUTPUT=$(npx eslint $RELATIVE_FILES --max-warnings 0 --no-warn-ignored 2>&1) || WORKSPACE_EXIT=$? | |
| if [ $WORKSPACE_EXIT -ne 0 ]; then | |
| LINT_EXIT_CODE=1 | |
| LINT_OUTPUT="${LINT_OUTPUT}\n### ${workspace_name} ($workspace_relative_path) Linting Issues\n\`\`\`\n${WORKSPACE_OUTPUT}\n\`\`\`\n" | |
| FAILED_WORKSPACES="${FAILED_WORKSPACES}- ${workspace_name} ($workspace_relative_path)\n" | |
| echo "$WORKSPACE_OUTPUT" | |
| echo "::warning::${workspace_name} linting failed, but continuing to check other workspaces..." | |
| else | |
| echo "✅ ${workspace_name} files passed linting" | |
| fi | |
| cd "$REPO_ROOT" | |
| echo "::endgroup::" | |
| fi | |
| done <<< "$WORKSPACES" | |
| # Log summary of failed workspaces if any | |
| if [ $LINT_EXIT_CODE -ne 0 ]; then | |
| echo "::error::Linting failed in the following workspaces:" | |
| echo -e "$FAILED_WORKSPACES" | while read line; do | |
| [ -n "$line" ] && echo "::error:: $line" | |
| done | |
| fi | |
| # Save output for comment | |
| if [ $LINT_EXIT_CODE -ne 0 ]; then | |
| echo "lint_failed=true" >> $GITHUB_OUTPUT | |
| echo -e "$LINT_OUTPUT" > /tmp/lint_output.txt | |
| else | |
| echo "lint_failed=false" >> $GITHUB_OUTPUT | |
| fi | |
| exit $LINT_EXIT_CODE | |
| - name: Comment on PR (Success) | |
| if: | | |
| always() && | |
| steps.changed-files.outputs.has_files == 'true' && | |
| steps.eslint.outputs.lint_failed != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ${{ steps.pr.outputs.number }}, | |
| body: '✅ All changed files passed linting checks!' | |
| }); | |
| - name: Comment on PR (Failure) | |
| if: | | |
| always() && | |
| steps.changed-files.outputs.has_files == 'true' && | |
| steps.eslint.outputs.lint_failed == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const lintOutput = fs.readFileSync('/tmp/lint_output.txt', 'utf8'); | |
| const body = `❌ Linting issues found in changed files:\n\n${lintOutput}\n\nPlease fix these issues before merging.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ${{ steps.pr.outputs.number }}, | |
| body: body | |
| }); | |
| - name: Comment on PR (No files) | |
| if: | | |
| always() && | |
| steps.changed-files.outputs.has_files == 'false' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: ${{ steps.pr.outputs.number }}, | |
| body: 'ℹ️ No lintable files were changed in this PR.' | |
| }); | |
| - name: Fail if linting failed | |
| if: steps.eslint.outputs.lint_failed == 'true' | |
| run: exit 1 |