From 116148165ca155a294e0fedbb7b538e402d52ac9 Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 11:54:50 -0400 Subject: [PATCH 1/6] Quality gates: coverage threshold, pre-commit, pytest markers, e2e smoke test - Add pytest-cov with 85% coverage threshold enforced in `make test` - Add .pre-commit-config.yaml (ruff, black, trailing whitespace, AST check, debug statements, private key detection, YAML/TOML validation) - Add pytest markers: unit (core+templates), integration (CLI), e2e (scaffold) - Add e2e smoke test validating full project scaffold output - Makefile: add venv, install-dev, update, test-unit, test-integration, test-e2e, test-fast, coverage, pre-commit, clean-all targets Closes #10, #11, #12, #13 Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 +- .pre-commit-config.yaml | 31 ++++++ Makefile | 75 +++++++++++-- pyproject.toml | 20 ++++ tests/test_cli/conftest.py | 9 ++ tests/test_core/conftest.py | 9 ++ tests/test_e2e/__init__.py | 0 tests/test_e2e/conftest.py | 9 ++ tests/test_e2e/test_smoke.py | 179 +++++++++++++++++++++++++++++++ tests/test_templates/conftest.py | 9 ++ 10 files changed, 336 insertions(+), 7 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 tests/test_cli/conftest.py create mode 100644 tests/test_core/conftest.py create mode 100644 tests/test_e2e/__init__.py create mode 100644 tests/test_e2e/conftest.py create mode 100644 tests/test_e2e/test_smoke.py create mode 100644 tests/test_templates/conftest.py diff --git a/.gitignore b/.gitignore index f996082..0f8111b 100644 --- a/.gitignore +++ b/.gitignore @@ -136,4 +136,4 @@ poetry.lock **/.DS_Store # VSC settings -.vscode/ \ No newline at end of file +.vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b035e29 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,31 @@ +fail_fast: true + +repos: + # General file hygiene + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + args: ['--maxkb=500'] + - id: check-ast + - id: debug-statements + - id: detect-private-key + - id: check-merge-conflict + + # Ruff — lint + import sorting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.6 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + + # Black — code formatting + - repo: https://github.com/psf/black + rev: 26.3.1 + hooks: + - id: black diff --git a/Makefile b/Makefile index f3c070e..f1ceb0d 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,65 @@ .DEFAULT_GOAL := help -.PHONY: install test test-verbose test-single lint format typecheck check clean help +VENV_DIR := .venv -install: ## Install dependencies +.PHONY: venv install install-dev update \ + test test-verbose test-single test-unit test-integration test-e2e test-fast \ + lint format typecheck check coverage \ + pre-commit-install pre-commit \ + clean clean-all help + +# --------------------------------------------------------------------------- +# Environment setup +# --------------------------------------------------------------------------- + +venv: ## Create virtual environment + python3 -m venv $(VENV_DIR) + $(VENV_DIR)/bin/pip install --upgrade pip + +install: ## Install all dependencies + pre-commit hooks poetry install + poetry run pre-commit install + +install-dev: venv install ## One-shot dev environment setup (new contributors start here) + @echo "\n✅ Dev environment ready. Run 'make check' to verify." + +update: ## Update dependencies to latest compatible versions + poetry update + poetry run pre-commit autoupdate -test: ## Run tests - poetry run pytest --no-header -q || test $$? -eq 5 +# --------------------------------------------------------------------------- +# Testing +# --------------------------------------------------------------------------- -test-verbose: ## Run tests with verbose output - poetry run pytest -v +test: ## Run tests with coverage (CI gate: fails if <85%) + poetry run pytest --cov --cov-fail-under=85 --no-header -q || test $$? -eq 5 + +test-verbose: ## Run tests with verbose output + coverage + poetry run pytest --cov test-single: ## Run a single test (usage: make test-single K=test_name) poetry run pytest -k "$(K)" +test-unit: ## Run only unit tests (core + templates) + poetry run pytest -m unit --no-header -q + +test-integration: ## Run only integration tests (CLI commands) + poetry run pytest -m integration --no-header -q + +test-e2e: ## Run only e2e tests (full workflow) + poetry run pytest -m e2e --no-header -q + +test-fast: ## Run all tests except slow ones + poetry run pytest -m "not slow" --no-header -q + +coverage: ## Generate HTML coverage report + poetry run pytest --cov --cov-report=html --no-header -q + @echo "Coverage report: htmlcov/index.html" + +# --------------------------------------------------------------------------- +# Code quality +# --------------------------------------------------------------------------- + lint: ## Check linting (ruff + black) poetry run ruff check . poetry run black --check . @@ -27,6 +73,16 @@ typecheck: ## Run type checking check: lint typecheck test ## Run all checks (CI gate) +pre-commit-install: ## Install pre-commit hooks into .git/hooks + poetry run pre-commit install + +pre-commit: ## Run pre-commit hooks on all files + poetry run pre-commit run --all-files + +# --------------------------------------------------------------------------- +# Cleanup +# --------------------------------------------------------------------------- + clean: ## Remove build artifacts and caches find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true find . -type d -name .pytest_cache -exec rm -rf {} + 2>/dev/null || true @@ -36,5 +92,12 @@ clean: ## Remove build artifacts and caches find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true rm -rf dist/ build/ .coverage +clean-all: clean ## Remove everything including virtual environment + rm -rf $(VENV_DIR) + +# --------------------------------------------------------------------------- +# Help +# --------------------------------------------------------------------------- + help: ## Show this help message @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/pyproject.toml b/pyproject.toml index de42511..558982a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ aiosqlite = ">=0.20.0" httpx = ">=0.28.0" mypy = ">=1.13.0" types-pyyaml = "^6.0.12.20250915" +pytest-cov = "^7.1.0" +pre-commit = "^4.5.1" [build-system] requires = ["poetry-core"] @@ -46,6 +48,24 @@ build-backend = "poetry.core.masonry.api" asyncio_mode = "auto" testpaths = ["tests"] addopts = "-v --tb=short" +markers = [ + "unit: Fast isolated tests (core, templates)", + "integration: Tests that scaffold projects in tmp dirs (CLI commands)", + "e2e: End-to-end workflow tests (init → add-entity → generate → list)", + "slow: Tests that take >1s", +] + +[tool.coverage.run] +source = ["faststack_core", "cli"] +omit = ["tests/*"] + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", + "if __name__ == .__main__.", +] [tool.ruff] target-version = "py312" diff --git a/tests/test_cli/conftest.py b/tests/test_cli/conftest.py new file mode 100644 index 0000000..f70703a --- /dev/null +++ b/tests/test_cli/conftest.py @@ -0,0 +1,9 @@ +"""Auto-mark all tests in test_cli/ as integration tests.""" + +import pytest + + +def pytest_collection_modifyitems(items): + for item in items: + if "/test_cli/" in str(item.fspath): + item.add_marker(pytest.mark.integration) diff --git a/tests/test_core/conftest.py b/tests/test_core/conftest.py new file mode 100644 index 0000000..738b821 --- /dev/null +++ b/tests/test_core/conftest.py @@ -0,0 +1,9 @@ +"""Auto-mark all tests in test_core/ as unit tests.""" + +import pytest + + +def pytest_collection_modifyitems(items): + for item in items: + if "/test_core/" in str(item.fspath): + item.add_marker(pytest.mark.unit) diff --git a/tests/test_e2e/__init__.py b/tests/test_e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_e2e/conftest.py b/tests/test_e2e/conftest.py new file mode 100644 index 0000000..e5fe564 --- /dev/null +++ b/tests/test_e2e/conftest.py @@ -0,0 +1,9 @@ +"""Auto-mark all tests in test_e2e/ as e2e tests.""" + +import pytest + + +def pytest_collection_modifyitems(items): + for item in items: + if "/test_e2e/" in str(item.fspath): + item.add_marker(pytest.mark.e2e) diff --git a/tests/test_e2e/test_smoke.py b/tests/test_e2e/test_smoke.py new file mode 100644 index 0000000..638d4b1 --- /dev/null +++ b/tests/test_e2e/test_smoke.py @@ -0,0 +1,179 @@ +"""End-to-end smoke test: scaffold a project and validate all generated code. + +Covers issue #13: generated project validation. +When Phase 6 entity CLI commands land, this test should be extended to cover +the full init → add-entity → generate → list workflow. +""" + +from __future__ import annotations + +import ast +from pathlib import Path + +import pytest +import yaml +from click.testing import CliRunner + +from cli import cli_group + + +@pytest.fixture +def runner(): + return CliRunner() + + +@pytest.fixture +def entities_yaml(tmp_path: Path) -> Path: + """Write a multi-entity YAML fixture.""" + content = """\ +entities: + User: + base: FullAuditedEntity + fields: + email: + type: string + required: true + unique: true + name: + type: string + required: true + role: + type: enum + values: [admin, editor, viewer] + default: '"viewer"' + searchable: [email, name] + + Post: + base: AuditedEntity + fields: + title: + type: string + required: true + content: + type: text + user_id: + type: uuid + references: User + searchable: [title] + + Category: + base: AuditedEntity + fields: + name: + type: string + required: true + parent_id: + type: uuid + references: self +""" + yaml_file = tmp_path / "entities.yaml" + yaml_file.write_text(content) + return yaml_file + + +class TestProjectScaffoldSmoke: + """Scaffold a full project and validate the structure.""" + + def test_init_succeeds(self, runner: CliRunner, tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + result = runner.invoke(cli_group, ["init", "blog"], catch_exceptions=False) + assert result.exit_code == 0 + assert "Created project" in result.output + + def test_all_expected_directories_exist( + self, runner: CliRunner, tmp_path: Path, monkeypatch + ) -> None: + monkeypatch.chdir(tmp_path) + runner.invoke(cli_group, ["init", "blog"], catch_exceptions=False) + project = tmp_path / "blog" + + expected_dirs = [ + "app/models", + "app/schemas", + "app/repositories", + "app/services", + "app/api/routes", + "tests/unit/fakes", + "tests/integration", + "tests/factories", + "alembic/versions", + ] + for d in expected_dirs: + assert (project / d).is_dir(), f"Missing directory: {d}" + + def test_project_files_exist(self, runner: CliRunner, tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + runner.invoke(cli_group, ["init", "blog"], catch_exceptions=False) + project = tmp_path / "blog" + + expected_files = [ + "app/main.py", + "app/config.py", + "pyproject.toml", + "Dockerfile", + "docker-compose.yml", + "alembic.ini", + "alembic/env.py", + "tests/conftest.py", + ".project-config.yaml", + ".env", + ] + for f in expected_files: + assert (project / f).is_file(), f"Missing: {f}" + + def test_all_generated_python_is_valid( + self, runner: CliRunner, tmp_path: Path, monkeypatch + ) -> None: + monkeypatch.chdir(tmp_path) + runner.invoke(cli_group, ["init", "blog"], catch_exceptions=False) + project = tmp_path / "blog" + + py_files = list(project.rglob("*.py")) + assert len(py_files) >= 15, f"Expected >=15 .py files, got {len(py_files)}" + + for py_file in py_files: + source = py_file.read_text() + if not source.strip(): + continue # skip empty __init__.py + try: + ast.parse(source) + except SyntaxError as e: + rel = py_file.relative_to(project) + pytest.fail(f"Invalid Python in {rel}: {e}") + + def test_project_config_has_structure( + self, runner: CliRunner, tmp_path: Path, monkeypatch + ) -> None: + monkeypatch.chdir(tmp_path) + runner.invoke(cli_group, ["init", "blog"], catch_exceptions=False) + + config = yaml.safe_load((tmp_path / "blog" / ".project-config.yaml").read_text()) + assert config["project_name"] == "blog" + assert config["architecture"] == "simple" + assert "entities" in config + + +class TestInitWithEntitiesSmoke: + """Scaffold with --entities and validate router registration.""" + + def test_init_with_entities_has_routers_in_main( + self, + runner: CliRunner, + tmp_path: Path, + monkeypatch, + entities_yaml: Path, + ) -> None: + monkeypatch.chdir(tmp_path) + result = runner.invoke( + cli_group, + ["init", "blog", "--entities", str(entities_yaml)], + catch_exceptions=False, + ) + assert result.exit_code == 0 + + main_content = (tmp_path / "blog" / "app" / "main.py").read_text() + assert "user_router" in main_content + assert "post_router" in main_content + assert "category_router" in main_content + assert "include_router" in main_content + ast.parse(main_content) diff --git a/tests/test_templates/conftest.py b/tests/test_templates/conftest.py new file mode 100644 index 0000000..58c0e7b --- /dev/null +++ b/tests/test_templates/conftest.py @@ -0,0 +1,9 @@ +"""Auto-mark all tests in test_templates/ as unit tests.""" + +import pytest + + +def pytest_collection_modifyitems(items): + for item in items: + if "/test_templates/" in str(item.fspath): + item.add_marker(pytest.mark.unit) From 71175ac1370113c6197e0121644cb6b651fb7daf Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 11:59:28 -0400 Subject: [PATCH 2/6] Add coverage + pre-commit + caching to CI workflow - Test job now enforces 85% coverage threshold and uploads coverage.xml - Add pre-commit job using pre-commit/action (caches hook envs) - Cache Poetry dependencies across all jobs - Skip draft PRs, cancel stale runs - Python matrix ready to expand to 3.13 Closes #15 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 72 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f612a5..f5ec28f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: [main] pull_request: + types: [opened, synchronize, ready_for_review] branches: [main] concurrency: @@ -15,7 +16,7 @@ permissions: jobs: lint: - name: Lint + name: Lint & Format runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft @@ -31,6 +32,13 @@ jobs: - name: Install Poetry run: pipx install poetry + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: ${{ runner.os }}-poetry- + - name: Install dependencies run: poetry install --no-interaction @@ -57,6 +65,13 @@ jobs: - name: Install Poetry run: pipx install poetry + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: ${{ runner.os }}-poetry- + - name: Install dependencies run: poetry install --no-interaction @@ -64,10 +79,16 @@ jobs: run: poetry run mypy faststack_core/ cli/ test: - name: Test + name: Test (py${{ matrix.python }}) runs-on: ubuntu-latest timeout-minutes: 10 if: github.event_name != 'pull_request' || !github.event.pull_request.draft + + strategy: + fail-fast: false + matrix: + python: ["3.12"] + steps: - uses: actions/checkout@v4 with: @@ -75,16 +96,57 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ matrix.python }} - name: Install Poetry run: pipx install poetry + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: ${{ runner.os }}-poetry- + - name: Install dependencies run: poetry install --no-interaction - - name: Run tests - run: poetry run pytest -v --tb=short || test $? -eq 5 + - name: Run tests with coverage + run: | + poetry run pytest \ + --cov --cov-report=term --cov-report=xml \ + --cov-fail-under=85 \ + -v --tb=short + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-py${{ matrix.python }} + path: coverage.xml + + pre-commit: + name: Pre-commit + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Cache pre-commit environments + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: ${{ runner.os }}-pre-commit- + + - uses: pre-commit/action@v3.0.1 package: name: Package Build From 9fdf02d2dde5d00072b23e8824e2c1d9fd745657 Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 12:05:36 -0400 Subject: [PATCH 3/6] Rewrite Makefile and CI workflows to match mcp-context-forge standards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Makefile: adopt ContextForge help system with section headers and emoji-prefixed categories (🌱 Installation, 🧪 Testing, 🔍 Quality, 🧹 Cleanup) - Makefile: add strict shell flags (-eu -o pipefail), project variables, author attribution - CI: add proper file headers with author and job descriptions - CI: add shared env block, section comments for each job - Dependency review: match documentation style Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 77 ++++++++-- .github/workflows/dependency-review.yml | 13 +- Makefile | 179 ++++++++++++++++-------- 3 files changed, 196 insertions(+), 73 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f5ec28f..524325e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,20 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# FastStack CI — Lint, Type Check, Test & Package +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# +# Author: Manav Gupta +# +# Jobs: +# lint — ruff + black formatting checks +# typecheck — mypy static type analysis +# test — pytest with 85% coverage threshold +# pre-commit — validate all pre-commit hooks +# package — build wheel/sdist and validate with twine +# +# Triggers on push to main and PRs. Skips draft PRs. +# Cancels stale runs on the same branch. +# + name: CI on: @@ -14,18 +31,31 @@ concurrency: permissions: contents: read +# --------------------------------------------------------------------------- +# Shared environment +# --------------------------------------------------------------------------- +env: + PYTHONUNBUFFERED: "1" + PIP_DISABLE_PIP_VERSION_CHECK: "1" + jobs: + + # ========================================================================= + # 🔍 Lint & Format + # ========================================================================= lint: name: Lint & Format runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-python@v5 + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: "3.12" @@ -48,17 +78,22 @@ jobs: - name: Black check run: poetry run black --check . + # ========================================================================= + # 🔎 Type Check + # ========================================================================= typecheck: name: Type Check runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-python@v5 + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: "3.12" @@ -78,6 +113,9 @@ jobs: - name: Mypy run: poetry run mypy faststack_core/ cli/ + # ========================================================================= + # 🧪 Tests & Coverage + # ========================================================================= test: name: Test (py${{ matrix.python }}) runs-on: ubuntu-latest @@ -90,11 +128,13 @@ jobs: python: ["3.12"] steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-python@v5 + - name: Setup Python ${{ matrix.python }} + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} @@ -114,7 +154,9 @@ jobs: - name: Run tests with coverage run: | poetry run pytest \ - --cov --cov-report=term --cov-report=xml \ + --cov \ + --cov-report=term \ + --cov-report=xml \ --cov-fail-under=85 \ -v --tb=short @@ -125,17 +167,22 @@ jobs: name: coverage-py${{ matrix.python }} path: coverage.xml + # ========================================================================= + # ✅ Pre-commit + # ========================================================================= pre-commit: name: Pre-commit runs-on: ubuntu-latest timeout-minutes: 10 if: github.event_name != 'pull_request' || !github.event.pull_request.draft steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-python@v5 + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: "3.12" @@ -146,19 +193,25 @@ jobs: key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} restore-keys: ${{ runner.os }}-pre-commit- - - uses: pre-commit/action@v3.0.1 + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 + # ========================================================================= + # 📦 Package Build + # ========================================================================= package: name: Package Build runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/setup-python@v5 + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: "3.12" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index a34ae96..880f481 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,3 +1,13 @@ +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# FastStack — Dependency Review +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# +# Author: Manav Gupta +# +# Scans dependency changes in PRs for known vulnerabilities and +# restricted licenses. Only runs when pyproject.toml or poetry.lock change. +# + name: Dependency Review on: @@ -18,7 +28,8 @@ jobs: timeout-minutes: 5 if: "!github.event.pull_request.draft" steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 with: persist-credentials: false diff --git a/Makefile b/Makefile index f1ceb0d..f255355 100644 --- a/Makefile +++ b/Makefile @@ -1,103 +1,162 @@ -.DEFAULT_GOAL := help - -VENV_DIR := .venv +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# FastStack — Hybrid FastAPI Framework +# Runtime core + CLI generator for async FastAPI projects +# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# +# Author: Manav Gupta +# Usage: run `make` or `make help` to view available targets +# +# help: FastStack (Hybrid FastAPI framework — runtime core + CLI generator) +# + +SHELL := /bin/bash +.SHELLFLAGS := -eu -o pipefail -c + +# ───────────────────────────────────────────────────────────────────────── +# Project variables +# ───────────────────────────────────────────────────────────────────────── +PROJECT_NAME := faststack +VENV_DIR := .venv +COVERAGE_MIN := 85 + +# Directories and files to clean +DIRS_TO_CLEAN := __pycache__ .pytest_cache .mypy_cache .ruff_cache \ + htmlcov dist build .eggs *.egg-info + +FILES_TO_CLEAN := .coverage coverage.xml + +# ============================================================================= +# DYNAMIC HELP +# ============================================================================= +.PHONY: help +help: + @grep "^# help\:" Makefile | grep -v grep | sed 's/\# help\: //' | sed 's/\# help\://' -.PHONY: venv install install-dev update \ - test test-verbose test-single test-unit test-integration test-e2e test-fast \ - lint format typecheck check coverage \ - pre-commit-install pre-commit \ - clean clean-all help +.DEFAULT_GOAL := help -# --------------------------------------------------------------------------- -# Environment setup -# --------------------------------------------------------------------------- +# ============================================================================= +# help: +# help: 🌱 VIRTUAL ENVIRONMENT & INSTALLATION +# ============================================================================= -venv: ## Create virtual environment +# help: venv - Create a fresh virtual environment +.PHONY: venv +venv: python3 -m venv $(VENV_DIR) $(VENV_DIR)/bin/pip install --upgrade pip -install: ## Install all dependencies + pre-commit hooks +# help: install - Install all dependencies +.PHONY: install +install: poetry install - poetry run pre-commit install -install-dev: venv install ## One-shot dev environment setup (new contributors start here) - @echo "\n✅ Dev environment ready. Run 'make check' to verify." +# help: install-dev - Full dev setup (venv + deps + pre-commit hooks) +.PHONY: install-dev +install-dev: venv install + poetry run pre-commit install + @echo "" + @echo "✅ Dev environment ready. Run 'make check' to verify." -update: ## Update dependencies to latest compatible versions +# help: update - Update dependencies to latest compatible versions +.PHONY: update +update: poetry update poetry run pre-commit autoupdate -# --------------------------------------------------------------------------- -# Testing -# --------------------------------------------------------------------------- +# ============================================================================= +# help: +# help: 🧪 TESTING +# ============================================================================= -test: ## Run tests with coverage (CI gate: fails if <85%) - poetry run pytest --cov --cov-fail-under=85 --no-header -q || test $$? -eq 5 +# help: test - Run tests with coverage (CI gate: fails if <$(COVERAGE_MIN)%) +.PHONY: test +test: + poetry run pytest --cov --cov-fail-under=$(COVERAGE_MIN) --no-header -q || test $$? -eq 5 -test-verbose: ## Run tests with verbose output + coverage +# help: test-verbose - Run tests with verbose output + coverage +.PHONY: test-verbose +test-verbose: poetry run pytest --cov -test-single: ## Run a single test (usage: make test-single K=test_name) +# help: test-single - Run a single test (usage: make test-single K=test_name) +.PHONY: test-single +test-single: poetry run pytest -k "$(K)" -test-unit: ## Run only unit tests (core + templates) +# help: test-unit - Run only unit tests (core + templates) +.PHONY: test-unit +test-unit: poetry run pytest -m unit --no-header -q -test-integration: ## Run only integration tests (CLI commands) +# help: test-integration - Run only integration tests (CLI commands) +.PHONY: test-integration +test-integration: poetry run pytest -m integration --no-header -q -test-e2e: ## Run only e2e tests (full workflow) +# help: test-e2e - Run only e2e tests (full workflow validation) +.PHONY: test-e2e +test-e2e: poetry run pytest -m e2e --no-header -q -test-fast: ## Run all tests except slow ones +# help: test-fast - Run all tests except slow ones +.PHONY: test-fast +test-fast: poetry run pytest -m "not slow" --no-header -q -coverage: ## Generate HTML coverage report - poetry run pytest --cov --cov-report=html --no-header -q +# help: coverage - Generate HTML + XML coverage report +.PHONY: coverage +coverage: + poetry run pytest --cov --cov-report=html --cov-report=xml --no-header -q @echo "Coverage report: htmlcov/index.html" -# --------------------------------------------------------------------------- -# Code quality -# --------------------------------------------------------------------------- +# ============================================================================= +# help: +# help: 🔍 CODE QUALITY +# ============================================================================= -lint: ## Check linting (ruff + black) +# help: lint - Check linting (ruff + black) +.PHONY: lint +lint: poetry run ruff check . poetry run black --check . -format: ## Auto-format code (ruff fix + black) +# help: format - Auto-format code (ruff fix + black) +.PHONY: format +format: poetry run ruff check --fix . poetry run black . -typecheck: ## Run type checking +# help: typecheck - Run static type checking (mypy) +.PHONY: typecheck +typecheck: poetry run mypy faststack_core/ cli/ -check: lint typecheck test ## Run all checks (CI gate) +# help: check - Run all checks: lint + typecheck + test (CI gate) +.PHONY: check +check: lint typecheck test -pre-commit-install: ## Install pre-commit hooks into .git/hooks - poetry run pre-commit install - -pre-commit: ## Run pre-commit hooks on all files +# help: pre-commit - Run pre-commit hooks on all files +.PHONY: pre-commit +pre-commit: poetry run pre-commit run --all-files -# --------------------------------------------------------------------------- -# Cleanup -# --------------------------------------------------------------------------- +# help: pre-commit-install - Install pre-commit hooks into .git/hooks +.PHONY: pre-commit-install +pre-commit-install: + poetry run pre-commit install -clean: ## Remove build artifacts and caches - find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true - find . -type d -name .pytest_cache -exec rm -rf {} + 2>/dev/null || true - find . -type d -name .mypy_cache -exec rm -rf {} + 2>/dev/null || true - find . -type d -name .ruff_cache -exec rm -rf {} + 2>/dev/null || true - find . -type d -name htmlcov -exec rm -rf {} + 2>/dev/null || true - find . -type d -name "*.egg-info" -exec rm -rf {} + 2>/dev/null || true - rm -rf dist/ build/ .coverage +# ============================================================================= +# help: +# help: 🧹 CLEANUP +# ============================================================================= -clean-all: clean ## Remove everything including virtual environment - rm -rf $(VENV_DIR) +# help: clean - Remove build artifacts and caches +.PHONY: clean +clean: + find . -type d \( -name __pycache__ -o -name .pytest_cache -o -name .mypy_cache -o -name .ruff_cache -o -name htmlcov -o -name "*.egg-info" \) -exec rm -rf {} + 2>/dev/null || true + rm -rf dist/ build/ .coverage coverage.xml -# --------------------------------------------------------------------------- -# Help -# --------------------------------------------------------------------------- - -help: ## Show this help message - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' +# help: clean-all - Remove everything including virtual environment +.PHONY: clean-all +clean-all: clean + rm -rf $(VENV_DIR) From b5285dfade0b8182378e9beed07200d057bb3f53 Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 12:17:33 -0400 Subject: [PATCH 4/6] Clean targets: show descriptive messages instead of raw commands Co-Authored-By: Claude Opus 4.6 (1M context) --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f255355..0366406 100644 --- a/Makefile +++ b/Makefile @@ -153,10 +153,14 @@ pre-commit-install: # help: clean - Remove build artifacts and caches .PHONY: clean clean: - find . -type d \( -name __pycache__ -o -name .pytest_cache -o -name .mypy_cache -o -name .ruff_cache -o -name htmlcov -o -name "*.egg-info" \) -exec rm -rf {} + 2>/dev/null || true - rm -rf dist/ build/ .coverage coverage.xml + @echo "🧹 Cleaning workspace..." + @find . -type d \( -name __pycache__ -o -name .pytest_cache -o -name .mypy_cache -o -name .ruff_cache -o -name htmlcov -o -name "*.egg-info" \) -exec rm -rf {} + 2>/dev/null || true + @rm -rf dist/ build/ .coverage coverage.xml + @echo "✅ Clean complete." # help: clean-all - Remove everything including virtual environment .PHONY: clean-all clean-all: clean - rm -rf $(VENV_DIR) + @echo "🗑️ Removing virtual environment..." + @rm -rf $(VENV_DIR) + @echo "✅ Full clean complete." From dde677cfeaec79fc76cdf0facc65bdf846a27b94 Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 12:21:32 -0400 Subject: [PATCH 5/6] CI workflows: add ContextForge-style documentation and structure - Add file-level doc blocks explaining what each workflow does - Add references section with links to tools - Add author attribution (Manav Gupta) - Add emoji-prefixed step names matching ContextForge conventions - Add inline section comments (Setup, Checks, Build & Validate) - Add principle-of-least-privilege permission comments - Extract PYTHON_VERSION to shared env block - Dependency review: add comment-summary-in-pr on failure Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 118 ++++++++++++++---------- .github/workflows/dependency-review.yml | 47 ++++++++-- 2 files changed, 108 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 524325e..5d718b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,25 @@ -# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -# FastStack CI — Lint, Type Check, Test & Package -# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# =============================================================== +# 🔧 CI Workflow — Lint, Type Check, Test, Pre-commit & Package +# =============================================================== # -# Author: Manav Gupta -# -# Jobs: -# lint — ruff + black formatting checks -# typecheck — mypy static type analysis -# test — pytest with 85% coverage threshold -# pre-commit — validate all pre-commit hooks -# package — build wheel/sdist and validate with twine +# This workflow: +# - Runs ruff + black format checks (lint) +# - Runs mypy static type analysis (typecheck) +# - Runs pytest with 85% coverage threshold (test) +# - Validates all pre-commit hooks (pre-commit) +# - Builds wheel/sdist and validates with twine (package) # -# Triggers on push to main and PRs. Skips draft PRs. +# All jobs run in parallel. Skips draft PRs. # Cancels stale runs on the same branch. # +# References +# ────────── +# - Poetry: https://python-poetry.org/docs/ +# - Ruff: https://docs.astral.sh/ruff/ +# - Pre-commit: https://pre-commit.com/ +# +# Author: Manav Gupta +# =============================================================== name: CI @@ -28,15 +34,19 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# ----------------------------------------------------------------- +# Minimal permissions — principle of least privilege +# ----------------------------------------------------------------- permissions: contents: read -# --------------------------------------------------------------------------- +# ----------------------------------------------------------------- # Shared environment -# --------------------------------------------------------------------------- +# ----------------------------------------------------------------- env: PYTHONUNBUFFERED: "1" PIP_DISABLE_PIP_VERSION_CHECK: "1" + PYTHON_VERSION: "3.12" jobs: @@ -48,34 +58,37 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft + steps: - - name: Checkout code + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Setup Python + - name: 🐍 Setup Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - - name: Install Poetry + - name: 📦 Install Poetry run: pipx install poetry - - name: Cache Poetry dependencies + - name: 💾 Cache Poetry dependencies uses: actions/cache@v4 with: path: ~/.cache/pypoetry key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} restore-keys: ${{ runner.os }}-poetry- - - name: Install dependencies + - name: 📦 Install dependencies run: poetry install --no-interaction - - name: Ruff check + # ─────────── Checks ─────────── + - name: 🔍 Ruff lint run: poetry run ruff check . - - name: Black check + - name: 🎨 Black format check run: poetry run black --check . # ========================================================================= @@ -86,31 +99,34 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft + steps: - - name: Checkout code + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Setup Python + - name: 🐍 Setup Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - - name: Install Poetry + - name: 📦 Install Poetry run: pipx install poetry - - name: Cache Poetry dependencies + - name: 💾 Cache Poetry dependencies uses: actions/cache@v4 with: path: ~/.cache/pypoetry key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} restore-keys: ${{ runner.os }}-poetry- - - name: Install dependencies + - name: 📦 Install dependencies run: poetry install --no-interaction - - name: Mypy + # ─────────── Check ─────────── + - name: 🔎 Mypy type check run: poetry run mypy faststack_core/ cli/ # ========================================================================= @@ -128,30 +144,32 @@ jobs: python: ["3.12"] steps: - - name: Checkout code + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Setup Python ${{ matrix.python }} + - name: 🐍 Setup Python ${{ matrix.python }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - - name: Install Poetry + - name: 📦 Install Poetry run: pipx install poetry - - name: Cache Poetry dependencies + - name: 💾 Cache Poetry dependencies uses: actions/cache@v4 with: path: ~/.cache/pypoetry key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} restore-keys: ${{ runner.os }}-poetry- - - name: Install dependencies + - name: 📦 Install dependencies run: poetry install --no-interaction - - name: Run tests with coverage + # ─────────── Test ─────────── + - name: 🧪 Run tests with coverage run: | poetry run pytest \ --cov \ @@ -160,7 +178,7 @@ jobs: --cov-fail-under=85 \ -v --tb=short - - name: Upload coverage report + - name: 📊 Upload coverage report if: always() uses: actions/upload-artifact@v4 with: @@ -175,25 +193,28 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 if: github.event_name != 'pull_request' || !github.event.pull_request.draft + steps: - - name: Checkout code + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Setup Python + - name: 🐍 Setup Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - - name: Cache pre-commit environments + - name: 💾 Cache pre-commit environments uses: actions/cache@v4 with: path: ~/.cache/pre-commit key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} restore-keys: ${{ runner.os }}-pre-commit- - - name: Run pre-commit + # ─────────── Check ─────────── + - name: ✅ Run pre-commit hooks uses: pre-commit/action@v3.0.1 # ========================================================================= @@ -204,24 +225,27 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 if: github.event_name != 'pull_request' || !github.event.pull_request.draft + steps: - - name: Checkout code + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Setup Python + - name: 🐍 Setup Python uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: ${{ env.PYTHON_VERSION }} - - name: Install Poetry + - name: 📦 Install Poetry run: pipx install poetry - - name: Build package + # ─────────── Build & Validate ─────────── + - name: 🔨 Build distributions run: poetry build - - name: Validate package + - name: ✅ Validate package metadata (twine) run: | pip install twine twine check dist/* diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 880f481..63685b5 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,12 +1,24 @@ -# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -# FastStack — Dependency Review -# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# =============================================================== +# 🔍 Dependency Review — Vulnerabilities & Licenses +# =============================================================== # -# Author: Manav Gupta +# This workflow: +# - Diffs dependency changes introduced by PRs to `main` +# - **Fails** when a change introduces either: +# ↳ A vulnerability of severity >= MODERATE +# ↳ A dependency under a strong-copyleft license incompatible +# with this project's MIT license (see deny-list below) +# +# Only runs when dependency files change (pyproject.toml, poetry.lock). +# Skips draft PRs. # -# Scans dependency changes in PRs for known vulnerabilities and -# restricted licenses. Only runs when pyproject.toml or poetry.lock change. +# References +# ────────── +# - Marketplace: https://github.com/marketplace/actions/dependency-review +# - Source code: https://github.com/github/dependency-review-action (MIT) # +# Author: Manav Gupta +# =============================================================== name: Dependency Review @@ -16,10 +28,14 @@ on: paths: - "pyproject.toml" - "poetry.lock" + - ".github/workflows/dependency-review.yml" +# ----------------------------------------------------------------- +# Minimal permissions — principle of least privilege +# ----------------------------------------------------------------- permissions: - contents: read - pull-requests: write + contents: read # for actions/checkout + pull-requests: write # post PR comment on failure jobs: dependency-review: @@ -27,14 +43,25 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 if: "!github.event.pull_request.draft" + steps: - - name: Checkout code + # ─────────── Checkout ─────────── + - name: ⬇️ Checkout code uses: actions/checkout@v4 with: persist-credentials: false - - name: Dependency Review + # ─────────── Scan ─────────── + - name: 🔍 Dependency & License gate uses: actions/dependency-review-action@v4 with: + # ───────── Vulnerability policy ───────── fail-on-severity: moderate + + # ───────── License policy ───────── + # Deny strong-copyleft licenses incompatible with MIT. + # LGPL/MPL are weak copyleft and allowed. deny-licenses: GPL-3.0, AGPL-3.0, SSPL-1.0 + + # ───────── UX ───────── + comment-summary-in-pr: on-failure From 35cc33413c5b5b3299e269032cd2509c9b74c94f Mon Sep 17 00:00:00 2001 From: manavgup Date: Mon, 6 Apr 2026 12:29:26 -0400 Subject: [PATCH 6/6] Split CI into separate workflows: lint, pre-commit, tests, dependency-review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lint.yml: ruff + black + mypy (path-filtered to **.py and pyproject.toml) - pre-commit.yml: all pre-commit hooks on PRs only - ci.yml → tests & package only (renamed to "Tests & Package") - dependency-review.yml: unchanged (already separate) Each workflow appears as an independent check on PRs, matching the ContextForge pattern of modular workflow files. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 138 ++++--------------------------- .github/workflows/lint.yml | 128 ++++++++++++++++++++++++++++ .github/workflows/pre-commit.yml | 68 +++++++++++++++ 3 files changed, 210 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/pre-commit.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d718b8..5a69797 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,27 +1,29 @@ # =============================================================== -# 🔧 CI Workflow — Lint, Type Check, Test, Pre-commit & Package +# 🧪 Tests & Coverage + 📦 Package Build # =============================================================== # # This workflow: -# - Runs ruff + black format checks (lint) -# - Runs mypy static type analysis (typecheck) -# - Runs pytest with 85% coverage threshold (test) -# - Validates all pre-commit hooks (pre-commit) -# - Builds wheel/sdist and validates with twine (package) +# - Runs pytest with 85% coverage threshold (test) +# - Uploads coverage.xml as artifact (test) +# - Builds wheel/sdist and validates with twine (package) # -# All jobs run in parallel. Skips draft PRs. +# Lint, type-check, and pre-commit live in separate workflows +# (lint.yml, pre-commit.yml) so they appear as independent +# checks on PRs. +# +# Triggers on push to main and PRs. Skips draft PRs. # Cancels stale runs on the same branch. # # References # ────────── -# - Poetry: https://python-poetry.org/docs/ -# - Ruff: https://docs.astral.sh/ruff/ -# - Pre-commit: https://pre-commit.com/ +# - Poetry: https://python-poetry.org/docs/ +# - Pytest: https://docs.pytest.org/ +# - Twine: https://twine.readthedocs.io/ # # Author: Manav Gupta # =============================================================== -name: CI +name: Tests & Package on: push: @@ -46,89 +48,9 @@ permissions: env: PYTHONUNBUFFERED: "1" PIP_DISABLE_PIP_VERSION_CHECK: "1" - PYTHON_VERSION: "3.12" jobs: - # ========================================================================= - # 🔍 Lint & Format - # ========================================================================= - lint: - name: Lint & Format - runs-on: ubuntu-latest - timeout-minutes: 5 - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - - steps: - # ─────────── Setup ─────────── - - name: ⬇️ Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: 🐍 Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: 📦 Install Poetry - run: pipx install poetry - - - name: 💾 Cache Poetry dependencies - uses: actions/cache@v4 - with: - path: ~/.cache/pypoetry - key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} - restore-keys: ${{ runner.os }}-poetry- - - - name: 📦 Install dependencies - run: poetry install --no-interaction - - # ─────────── Checks ─────────── - - name: 🔍 Ruff lint - run: poetry run ruff check . - - - name: 🎨 Black format check - run: poetry run black --check . - - # ========================================================================= - # 🔎 Type Check - # ========================================================================= - typecheck: - name: Type Check - runs-on: ubuntu-latest - timeout-minutes: 5 - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - - steps: - # ─────────── Setup ─────────── - - name: ⬇️ Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: 🐍 Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: 📦 Install Poetry - run: pipx install poetry - - - name: 💾 Cache Poetry dependencies - uses: actions/cache@v4 - with: - path: ~/.cache/pypoetry - key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} - restore-keys: ${{ runner.os }}-poetry- - - - name: 📦 Install dependencies - run: poetry install --no-interaction - - # ─────────── Check ─────────── - - name: 🔎 Mypy type check - run: poetry run mypy faststack_core/ cli/ - # ========================================================================= # 🧪 Tests & Coverage # ========================================================================= @@ -185,38 +107,6 @@ jobs: name: coverage-py${{ matrix.python }} path: coverage.xml - # ========================================================================= - # ✅ Pre-commit - # ========================================================================= - pre-commit: - name: Pre-commit - runs-on: ubuntu-latest - timeout-minutes: 10 - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - - steps: - # ─────────── Setup ─────────── - - name: ⬇️ Checkout code - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: 🐍 Setup Python - uses: actions/setup-python@v5 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: 💾 Cache pre-commit environments - uses: actions/cache@v4 - with: - path: ~/.cache/pre-commit - key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - restore-keys: ${{ runner.os }}-pre-commit- - - # ─────────── Check ─────────── - - name: ✅ Run pre-commit hooks - uses: pre-commit/action@v3.0.1 - # ========================================================================= # 📦 Package Build # ========================================================================= @@ -236,7 +126,7 @@ jobs: - name: 🐍 Setup Python uses: actions/setup-python@v5 with: - python-version: ${{ env.PYTHON_VERSION }} + python-version: "3.12" - name: 📦 Install Poetry run: pipx install poetry diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..77ef0ab --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,128 @@ +# =============================================================== +# 🔍 Lint & Static Analysis — Code Quality Gate +# =============================================================== +# +# This workflow: +# - Runs ruff for linting and import sorting +# - Runs black for code formatting checks +# - Runs mypy for static type analysis +# +# Triggers on push to main and PRs. Skips draft PRs. +# Cancels stale runs on the same branch. +# +# References +# ────────── +# - Ruff: https://docs.astral.sh/ruff/ +# - Black: https://black.readthedocs.io/ +# - Mypy: https://mypy.readthedocs.io/ +# +# Author: Manav Gupta +# =============================================================== + +name: Lint & Static Analysis + +on: + push: + branches: [main] + paths: + - "**.py" + - "pyproject.toml" + - ".github/workflows/lint.yml" + pull_request: + types: [opened, synchronize, ready_for_review] + branches: [main] + paths: + - "**.py" + - "pyproject.toml" + - ".github/workflows/lint.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# ----------------------------------------------------------------- +# Minimal permissions — principle of least privilege +# ----------------------------------------------------------------- +permissions: + contents: read + +jobs: + + # ========================================================================= + # 🔍 Ruff + Black + # ========================================================================= + python-lint: + name: Ruff & Black + runs-on: ubuntu-latest + timeout-minutes: 5 + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + + steps: + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: 🐍 Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: 📦 Install Poetry + run: pipx install poetry + + - name: 💾 Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: ${{ runner.os }}-poetry- + + - name: 📦 Install dependencies + run: poetry install --no-interaction + + # ─────────── Checks ─────────── + - name: 🔍 Ruff lint + run: poetry run ruff check . + + - name: 🎨 Black format check + run: poetry run black --check . + + # ========================================================================= + # 🔎 Type Check + # ========================================================================= + typecheck: + name: Mypy + runs-on: ubuntu-latest + timeout-minutes: 5 + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + + steps: + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: 🐍 Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: 📦 Install Poetry + run: pipx install poetry + + - name: 💾 Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }} + restore-keys: ${{ runner.os }}-poetry- + + - name: 📦 Install dependencies + run: poetry install --no-interaction + + # ─────────── Check ─────────── + - name: 🔎 Mypy type check + run: poetry run mypy faststack_core/ cli/ diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..d941f7c --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,68 @@ +# =============================================================== +# ✅ Pre-commit Checks +# =============================================================== +# +# This workflow: +# - Runs all pre-commit hooks against the full codebase +# - Validates: trailing whitespace, EOF, YAML/TOML syntax, +# Python AST, debug statements, private key detection, +# ruff lint, black formatting +# +# Only runs on PRs (not push to main — the lint workflow covers that). +# Skips draft PRs. Cancels stale runs. +# +# References +# ────────── +# - Pre-commit: https://pre-commit.com/ +# - pre-commit/action: https://github.com/pre-commit/action +# +# Author: Manav Gupta +# =============================================================== + +name: Pre-commit Checks + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# ----------------------------------------------------------------- +# Minimal permissions — principle of least privilege +# ----------------------------------------------------------------- +permissions: + contents: read + +jobs: + pre-commit: + name: Run pre-commit hooks + runs-on: ubuntu-latest + timeout-minutes: 10 + if: "!github.event.pull_request.draft" + + steps: + # ─────────── Setup ─────────── + - name: ⬇️ Checkout code + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + + - name: 🐍 Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: 💾 Cache pre-commit environments + uses: actions/cache@v4 + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: ${{ runner.os }}-pre-commit- + + # ─────────── Check ─────────── + - name: ✅ Run pre-commit hooks + uses: pre-commit/action@v3.0.1