feat: add initial ci.yml workflow #3
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| # Discover all projects in the repository | |
| discover: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| node-projects: ${{ steps.find-projects.outputs.node-projects }} | |
| poetry-projects: ${{ steps.find-projects.outputs.poetry-projects }} | |
| uv-projects: ${{ steps.find-projects.outputs.uv-projects }} | |
| pip-projects: ${{ steps.find-projects.outputs.pip-projects }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Find all projects | |
| id: find-projects | |
| run: | | |
| # Find Node.js projects (have package.json but not in node_modules) | |
| NODE_PROJECTS=$(find . -name "package.json" -not -path "*/node_modules/*" -exec dirname {} \; | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "node-projects=$NODE_PROJECTS" >> $GITHUB_OUTPUT | |
| # Find Poetry projects (have poetry.lock) | |
| POETRY_PROJECTS=$(find . -name "poetry.lock" -exec dirname {} \; | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "poetry-projects=$POETRY_PROJECTS" >> $GITHUB_OUTPUT | |
| # Find uv projects (have uv.lock) | |
| UV_PROJECTS=$(find . -name "uv.lock" -exec dirname {} \; | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "uv-projects=$UV_PROJECTS" >> $GITHUB_OUTPUT | |
| # Find pip projects (have requirements.txt but no poetry.lock or uv.lock) | |
| PIP_PROJECTS=$(find . -name "requirements.txt" -exec dirname {} \; | while read dir; do | |
| if [ ! -f "$dir/poetry.lock" ] && [ ! -f "$dir/uv.lock" ]; then | |
| echo "$dir" | |
| fi | |
| done | sort -u | jq -R -s -c 'split("\n") | map(select(length > 0))') | |
| echo "pip-projects=$PIP_PROJECTS" >> $GITHUB_OUTPUT | |
| # Debug output | |
| echo "Node.js projects: $NODE_PROJECTS" | |
| echo "Poetry projects: $POETRY_PROJECTS" | |
| echo "uv projects: $UV_PROJECTS" | |
| echo "pip projects: $PIP_PROJECTS" | |
| # Build and lint Node.js projects | |
| node: | |
| needs: discover | |
| if: ${{ needs.discover.outputs.node-projects != '[]' && needs.discover.outputs.node-projects != '' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| project: ${{ fromJson(needs.discover.outputs.node-projects) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Get npm cache directory | |
| id: npm-cache-dir | |
| shell: bash | |
| run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT | |
| - name: Cache npm dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.npm-cache-dir.outputs.dir }} | |
| key: ${{ runner.os }}-node-${{ hashFiles(format('{0}/package-lock.json', matrix.project), format('{0}/package.json', matrix.project)) }} | |
| restore-keys: | | |
| ${{ runner.os }}-node- | |
| - name: Install dependencies | |
| working-directory: ${{ matrix.project }} | |
| run: npm install | |
| - name: Build | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if npm run build --if-present 2>/dev/null; then | |
| echo "Build completed successfully" | |
| else | |
| echo "No build script found, skipping..." | |
| fi | |
| - name: Lint | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if npm run lint --if-present 2>/dev/null; then | |
| echo "Lint completed successfully" | |
| else | |
| echo "No lint script found, skipping..." | |
| fi | |
| # Build and lint Poetry projects | |
| poetry: | |
| needs: discover | |
| if: ${{ needs.discover.outputs.poetry-projects != '[]' && needs.discover.outputs.poetry-projects != '' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| project: ${{ fromJson(needs.discover.outputs.poetry-projects) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Poetry | |
| uses: snok/install-poetry@v1 | |
| with: | |
| version: latest | |
| virtualenvs-create: true | |
| virtualenvs-in-project: true | |
| - name: Cache Poetry dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ matrix.project }}/.venv | |
| key: ${{ runner.os }}-poetry-${{ hashFiles(format('{0}/poetry.lock', matrix.project)) }} | |
| restore-keys: | | |
| ${{ runner.os }}-poetry- | |
| - name: Install dependencies | |
| working-directory: ${{ matrix.project }} | |
| run: poetry install --no-interaction | |
| - name: Lint with ruff (if available) | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if poetry run ruff check . 2>/dev/null; then | |
| echo "Ruff lint completed" | |
| elif poetry run flake8 . 2>/dev/null; then | |
| echo "Flake8 lint completed" | |
| else | |
| echo "No Python linter found, skipping..." | |
| fi | |
| continue-on-error: true | |
| - name: Type check with mypy (if available) | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if poetry run mypy . 2>/dev/null; then | |
| echo "Type check completed" | |
| else | |
| echo "No type checker found, skipping..." | |
| fi | |
| continue-on-error: true | |
| # Build and lint uv projects | |
| uv: | |
| needs: discover | |
| if: ${{ needs.discover.outputs.uv-projects != '[]' && needs.discover.outputs.uv-projects != '' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| project: ${{ fromJson(needs.discover.outputs.uv-projects) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Install dependencies | |
| working-directory: ${{ matrix.project }} | |
| run: uv sync | |
| - name: Lint with ruff (if available) | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if uv run ruff check . 2>/dev/null; then | |
| echo "Ruff lint completed" | |
| elif uv run flake8 . 2>/dev/null; then | |
| echo "Flake8 lint completed" | |
| else | |
| echo "No Python linter found, skipping..." | |
| fi | |
| continue-on-error: true | |
| - name: Type check with mypy (if available) | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if uv run mypy . 2>/dev/null; then | |
| echo "Type check completed" | |
| else | |
| echo "No type checker found, skipping..." | |
| fi | |
| continue-on-error: true | |
| # Build and lint pip projects | |
| pip: | |
| needs: discover | |
| if: ${{ needs.discover.outputs.pip-projects != '[]' && needs.discover.outputs.pip-projects != '' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| project: ${{ fromJson(needs.discover.outputs.pip-projects) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Detect Python version | |
| id: python-version | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| if [ -f ".python-version" ]; then | |
| VERSION=$(cat .python-version | tr -d '[:space:]') | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| else | |
| echo "version=3.12" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ steps.python-version.outputs.version }} | |
| - name: Cache pip dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles(format('{0}/requirements.txt', matrix.project)) }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| working-directory: ${{ matrix.project }} | |
| run: | | |
| python -m pip install --upgrade pip | |
| # Use project-level pip.conf if it exists | |
| if [ -f "pip.conf" ]; then | |
| export PIP_CONFIG_FILE="$(pwd)/pip.conf" | |
| fi | |
| pip install -r requirements.txt | |
| - name: Install linters | |
| run: pip install ruff flake8 | |
| continue-on-error: true | |
| - name: Lint with ruff | |
| working-directory: ${{ matrix.project }} | |
| run: ruff check . || echo "Ruff lint skipped or failed" | |
| continue-on-error: true | |
| # Summary job to ensure all checks passed | |
| ci-success: | |
| needs: [node, poetry, uv, pip] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check all jobs passed | |
| run: | | |
| if [ "${{ needs.node.result }}" == "failure" ] || \ | |
| [ "${{ needs.poetry.result }}" == "failure" ] || \ | |
| [ "${{ needs.uv.result }}" == "failure" ] || \ | |
| [ "${{ needs.pip.result }}" == "failure" ]; then | |
| echo "One or more jobs failed" | |
| exit 1 | |
| fi | |
| echo "All CI checks passed!" |