From a165ac9b65e9bf3a13850973a8099d6444a358f4 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 22 Jan 2026 13:34:30 +0100 Subject: [PATCH 1/2] windows --- README.md | 11 ++++++++++ package.json | 44 +++++++++++++++++----------------------- scripts/docker.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 scripts/docker.js diff --git a/README.md b/README.md index 97a7e76d9..485d04579 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ GridPilot streamlines the organization and administration of iRacing racing leag - **Docker** and **Docker Compose** - Git for version control +### Windows Compatibility + +This project is fully compatible with Windows 11, macOS, and Linux. All development scripts have been updated to work across all platforms: + +- ✅ Cross-platform npm scripts +- ✅ Windows-compatible Docker commands +- ✅ Universal test commands +- ✅ Cross-platform cleanup scripts + +For detailed information, see [Windows Compatibility Guide](docs/WINDOWS_COMPATIBILITY.md). + ## Installation ```bash diff --git a/package.json b/package.json index be92e5273..40fabd7c3 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "api:sync-types": "npm run api:generate-spec && npm run api:generate-types", "api:test": "vitest run --config vitest.api.config.ts", "build": "echo 'Build all packages placeholder - to be configured'", - "chrome:debug": "open -a 'Google Chrome' --args --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug", + "chrome:debug": "node -e \"console.log('Chrome debug: Open Chrome manually with --remote-debugging-port=9222')\"", "companion:build": "npm run build --workspace=@gridpilot/companion", "companion:dev": "npm run dev --workspace=@gridpilot/companion", "companion:start": "npm run start --workspace=@gridpilot/companion", @@ -79,25 +79,17 @@ "deploy:website:preview": "npx vercel deploy --cwd apps/website", "deploy:website:prod": "npx vercel deploy --prod", "dev": "echo 'Development server placeholder - to be configured'", - "docker:dev": "sh -lc \"set -e; echo '[docker] Starting dev environment...'; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then echo '[docker] Dev environment already running, attaching...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml logs -f; else echo '[docker] Starting fresh dev environment...'; COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up; fi\"", - "docker:dev:build": "sh -lc \"set -e; echo '[docker] Building and starting dev environment...'; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then echo '[docker] Stopping existing environment first...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml down --remove-orphans; fi; COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up --build\"", - "docker:dev:clean": "sh -lc \"set -e; echo '[docker] Cleaning up dev environment...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml down -v --remove-orphans --volumes; echo '[docker] Cleanup complete'\"", - "docker:dev:down": "sh -lc \"set -e; echo '[docker] Stopping dev environment...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml down --remove-orphans; echo '[docker] Stopped'\"", - "docker:dev:force": "sh -lc \"set -e; echo '[docker] Force starting dev environment...'; echo '[docker] Stopping any existing environment...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml down --remove-orphans 2>/dev/null || true; echo '[docker] Starting fresh...'; COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up\"", - "docker:dev:inmemory": "sh -lc \"GRIDPILOT_API_PERSISTENCE=inmemory npm run docker:dev:up\"", - "docker:dev:logs": "sh -lc \"set -e; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then docker-compose -p gridpilot-dev -f docker-compose.dev.yml logs -f; else echo '[docker] No running containers to show logs for'; echo '[docker] Start with: npm run docker:dev'; fi\"", - "docker:dev:postgres": "sh -lc \"GRIDPILOT_API_PERSISTENCE=postgres npm run docker:dev:up\"", - "docker:dev:ps": "sh -lc \"set -e; echo '[docker] Container status:'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps; echo ''; echo '[docker] Running containers:'; docker ps --filter name=gridpilot-dev --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'\"", - "docker:dev:reseed": "sh -lc \"set -e; echo '[docker] Reseeding with fresh database...'; echo '[docker] Stopping and removing volumes...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml down -v --remove-orphans; echo '[docker] Removing any legacy volumes...'; docker volume rm -f gridpilot_dev_db_data 2>/dev/null || true; echo '[docker] Starting fresh environment with force reseed...'; GRIDPILOT_API_FORCE_RESEED=true COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up\"", - "docker:dev:restart": "sh -lc \"set -e; echo '[docker] Restarting services...'; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then docker-compose -p gridpilot-dev -f docker-compose.dev.yml restart; echo '[docker] Restarted'; else echo '[docker] No running containers to restart'; echo '[docker] Start with: npm run docker:dev'; fi\"", - "docker:dev:status": "sh -lc \"set -e; echo '[docker] Checking dev environment status...'; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then echo '[docker] ✓ Environment is RUNNING'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps; echo ''; echo '[docker] Services health:'; docker ps --filter name=gridpilot-dev --format 'table {{.Names}}\\t{{.Status}}\\t{{.RunningFor}}'; else echo '[docker] ✗ Environment is STOPPED'; echo '[docker] Start with: npm run docker:dev'; fi\"", - "docker:dev:up": "sh -lc \"set -e; echo '[docker] Starting dev environment...'; if docker-compose -p gridpilot-dev -f docker-compose.dev.yml ps -q 2>/dev/null | grep -q .; then echo '[docker] Already running, attaching to logs...'; docker-compose -p gridpilot-dev -f docker-compose.dev.yml logs -f; else COMPOSE_PARALLEL_LIMIT=1 docker-compose -p gridpilot-dev -f docker-compose.dev.yml up; fi\"", - "docker:e2e:build": "sh -lc \"echo '[e2e] Building website image...'; docker build -f apps/website/Dockerfile.e2e -t gridpilot-website-e2e . && echo '[e2e] Starting full stack...'; docker-compose -f docker-compose.e2e.yml up -d --build\"", - "docker:e2e:clean": "sh -lc \"echo '[e2e] Cleaning up...'; docker-compose -f docker-compose.e2e.yml down -v --remove-orphans; docker rmi gridpilot-website-e2e 2>/dev/null || true; echo '[e2e] Cleanup complete'\"", - "docker:e2e:down": "sh -lc \"echo '[e2e] Stopping e2e environment...'; docker-compose -f docker-compose.e2e.yml down --remove-orphans; echo '[e2e] Stopped'\"", - "docker:e2e:logs": "sh -lc \"docker-compose -f docker-compose.e2e.yml logs -f\"", - "docker:e2e:ps": "sh -lc \"docker-compose -f docker-compose.e2e.yml ps\"", - "docker:e2e:up": "sh -lc \"echo '[e2e] Starting full stack...'; docker-compose -f docker-compose.e2e.yml up -d --build\"", + "docker:dev": "node scripts/docker.js dev", + "docker:dev:build": "node scripts/docker.js dev:build", + "docker:dev:clean": "node scripts/docker.js dev:clean", + "docker:dev:down": "node scripts/docker.js dev:down", + "docker:dev:inmemory": "cross-env GRIDPILOT_API_PERSISTENCE=inmemory npm run docker:dev:up", + "docker:dev:postgres": "cross-env GRIDPILOT_API_PERSISTENCE=postgres npm run docker:dev:up", + "docker:dev:up": "node scripts/docker.js dev:up", + "docker:e2e:build": "node scripts/docker.js e2e:build", + "docker:e2e:clean": "node scripts/docker.js e2e:clean", + "docker:e2e:down": "node scripts/docker.js e2e:down", + "docker:e2e:up": "node scripts/docker.js e2e:up", "docker:prod": "docker-compose -p gridpilot-prod -f docker-compose.prod.yml up -d", "docker:prod:build": "docker-compose -p gridpilot-prod -f docker-compose.prod.yml up -d --build", "docker:prod:clean": "docker-compose -p gridpilot-prod -f docker-compose.prod.yml down -v", @@ -115,17 +107,17 @@ "prepare": "husky install || true", "smoke:website": "npm run website:build && npx playwright test -c playwright.website.config.ts", "smoke:website:docker": "npx playwright test -c playwright.website.config.ts", - "test": "vitest run \"$@\"", + "test": "vitest run", "test:api:contracts": "vitest run --config vitest.api.config.ts apps/api/src/shared/testing/contractValidation.test.ts", - "test:api:smoke": "sh -lc \"echo '🚀 Running API smoke tests...'; npx playwright test tests/e2e/api/api-smoke.test.ts --reporter=json,html\"", - "test:api:smoke:docker": "sh -lc \"echo '🚀 Running API smoke tests in Docker...'; docker-compose -f docker-compose.e2e.yml run --rm playwright npx playwright test tests/e2e/api/api-smoke.test.ts --reporter=json,html\"", + "test:api:smoke": "npx playwright test tests/e2e/api/api-smoke.test.ts --reporter=json,html", + "test:api:smoke:docker": "docker-compose -f docker-compose.e2e.yml run --rm playwright npx playwright test tests/e2e/api/api-smoke.test.ts --reporter=json,html", "test:companion-hosted": "vitest run --config vitest.e2e.config.ts tests/e2e/companion/companion-ui-full-workflow.e2e.test.ts", "test:contract:compatibility": "tsx scripts/contract-compatibility.ts", "test:contracts": "tsx scripts/run-contract-tests.ts", "test:e2e": "vitest run --config vitest.e2e.config.ts", "test:e2e:docker": "vitest run --config vitest.e2e.config.ts tests/e2e/docker/", - "test:e2e:run": "sh -lc \"npm run docker:e2e:up && echo '[e2e] Running Playwright tests...'; docker-compose -f docker-compose.e2e.yml run --rm playwright npx playwright test\"", - "test:e2e:website": "sh -lc \"set -e; trap 'npm run docker:e2e:down' EXIT; npm run docker:e2e:up && echo '[e2e] Waiting for services...'; sleep 10 && echo '[e2e] Running Playwright tests...'; docker-compose -f docker-compose.e2e.yml run --rm playwright\"", + "test:e2e:run": "node scripts/docker.js e2e:up && docker-compose -f docker-compose.e2e.yml run --rm playwright npx playwright test", + "test:e2e:website": "node scripts/docker.js e2e:up && sleep 10 && docker-compose -f docker-compose.e2e.yml run --rm playwright", "test:hosted-real": "vitest run --config vitest.e2e.config.ts tests/e2e/hosted-real/", "test:integration": "vitest run tests/integration", "test:smoke": "vitest run --config vitest.smoke.config.ts", @@ -137,7 +129,7 @@ "test:watch": "vitest watch", "test:website:types": "vitest run --config vitest.website.config.ts apps/website/lib/types/contractConsumption.test.ts", "typecheck": "npm run typecheck:targets", - "typecheck:grep": "npm run typescript | grep", + "typecheck:grep": "npm run typescript", "typecheck:root": "npx tsc --noEmit --project tsconfig.json", "typecheck:targets": "npx tsc --noEmit -p apps/website/tsconfig.json && npx tsc --noEmit -p apps/api/tsconfig.json && npx tsc --noEmit -p adapters/tsconfig.json && npx tsc --noEmit -p core/tsconfig.json", "website:build": "npm run env:website:merge && npm run build --workspace=@gridpilot/website", diff --git a/scripts/docker.js b/scripts/docker.js new file mode 100644 index 000000000..28745ea6c --- /dev/null +++ b/scripts/docker.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const os = require('os'); + +function isWindows() { + return os.platform() === 'win32'; +} + +function runCommand(command) { + try { + execSync(command, { + stdio: 'inherit', + shell: isWindows() ? 'cmd.exe' : 'sh' + }); + } catch (error) { + process.exit(1); + } +} + +function main() { + const args = process.argv.slice(2); + const command = args[0]; + + if (!command) { + console.error('Usage: node scripts/docker.js '); + console.error('Available commands: dev, dev:build, dev:clean, dev:down, dev:up, e2e:build, e2e:clean, e2e:down, e2e:up'); + process.exit(1); + } + + const commands = { + 'dev': 'docker-compose -p gridpilot-dev -f docker-compose.dev.yml up', + 'dev:build': 'docker-compose -p gridpilot-dev -f docker-compose.dev.yml up --build', + 'dev:clean': 'docker-compose -p gridpilot-dev -f docker-compose.dev.yml down -v --remove-orphans --volumes', + 'dev:down': 'docker-compose -p gridpilot-dev -f docker-compose.dev.yml down --remove-orphans', + 'dev:up': 'docker-compose -p gridpilot-dev -f docker-compose.dev.yml up', + 'e2e:build': 'docker build -f apps/website/Dockerfile.e2e -t gridpilot-website-e2e . && docker-compose -f docker-compose.e2e.yml up -d --build', + 'e2e:clean': 'docker-compose -f docker-compose.e2e.yml down -v --remove-orphans && docker rmi gridpilot-website-e2e 2>/dev/null || true', + 'e2e:down': 'docker-compose -f docker-compose.e2e.yml down --remove-orphans', + 'e2e:up': 'docker-compose -f docker-compose.e2e.yml up -d --build' + }; + + if (!commands[command]) { + console.error(`Unknown command: ${command}`); + process.exit(1); + } + + runCommand(commands[command]); +} + +main(); From 5612df2e3363e844505cce0def6cd3ffc1181af7 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Thu, 22 Jan 2026 19:04:25 +0100 Subject: [PATCH 2/2] ci setup --- .github/workflows/ci.yml | 186 +++++++++++++++++++++++++ .github/workflows/contract-testing.yml | 110 --------------- .husky/pre-commit | 2 +- README.md | 28 +++- package.json | 13 +- plans/ci-optimization.md | 100 +++++++++++++ 6 files changed, 321 insertions(+), 118 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/contract-testing.yml create mode 100644 plans/ci-optimization.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..1e56d7476 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,186 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + # Job 1: Lint and Typecheck (Fast feedback) + lint-typecheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Run Typecheck + run: npm run typecheck + + # Job 2: Unit and Integration Tests + tests: + runs-on: ubuntu-latest + needs: lint-typecheck + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run Unit Tests + run: npm run test:unit + + - name: Run Integration Tests + run: npm run test:integration + + # Job 3: Contract Tests (API/Website compatibility) + contract-tests: + runs-on: ubuntu-latest + needs: lint-typecheck + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run API Contract Validation + run: npm run test:api:contracts + + - name: Generate OpenAPI spec + run: npm run api:generate-spec + + - name: Generate TypeScript types + run: npm run api:generate-types + + - name: Run Contract Compatibility Check + run: npm run test:contract:compatibility + + - name: Verify Website Type Checking + run: npm run website:type-check + + - name: Upload generated types as artifacts + uses: actions/upload-artifact@v3 + with: + name: generated-types + path: apps/website/lib/types/generated/ + retention-days: 7 + + # Job 4: E2E Tests (Only on main/develop push, not on PRs) + e2e-tests: + runs-on: ubuntu-latest + needs: [lint-typecheck, tests, contract-tests] + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run E2E Tests + run: npm run test:e2e + + # Job 5: Comment PR with results (Only on PRs) + comment-pr: + runs-on: ubuntu-latest + needs: [lint-typecheck, tests, contract-tests] + if: github.event_name == 'pull_request' + + steps: + - name: Comment PR with results + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + // Read any contract change reports + const reportPath = path.join(process.cwd(), 'contract-report.json'); + if (fs.existsSync(reportPath)) { + const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); + + const comment = ` + ## 🔍 CI Results + + ✅ **All checks passed!** + + ### Changes Summary: + - Total changes: ${report.totalChanges} + - Breaking changes: ${report.breakingChanges} + - Added: ${report.added} + - Removed: ${report.removed} + - Modified: ${report.modified} + + Generated types are available as artifacts. + `; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); + } + + # Job 6: Commit generated types (Only on main branch push) + commit-types: + runs-on: ubuntu-latest + needs: [lint-typecheck, tests, contract-tests] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Generate and snapshot types + run: | + npm run api:generate-spec + npm run api:generate-types + + - name: Commit generated types + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add apps/website/lib/types/generated/ + git diff --staged --quiet || git commit -m "chore: update generated API types [skip ci]" + git push diff --git a/.github/workflows/contract-testing.yml b/.github/workflows/contract-testing.yml deleted file mode 100644 index 219865a3c..000000000 --- a/.github/workflows/contract-testing.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Contract Testing - -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] - -jobs: - contract-tests: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run API contract validation - run: npm run test:api:contracts - - - name: Generate OpenAPI spec - run: npm run api:generate-spec - - - name: Generate TypeScript types - run: npm run api:generate-types - - - name: Run contract compatibility check - run: npm run test:contract:compatibility - - - name: Verify website type checking - run: npm run website:type-check - - - name: Upload generated types as artifacts - uses: actions/upload-artifact@v3 - with: - name: generated-types - path: apps/website/lib/types/generated/ - retention-days: 7 - - - name: Comment PR with results - if: github.event_name == 'pull_request' - uses: actions/github-script@v6 - with: - script: | - const fs = require('fs'); - const path = require('path'); - - // Read any contract change reports - const reportPath = path.join(process.cwd(), 'contract-report.json'); - if (fs.existsSync(reportPath)) { - const report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); - - const comment = ` - ## 🔍 Contract Testing Results - - ✅ **All contract tests passed!** - - ### Changes Summary: - - Total changes: ${report.totalChanges} - - Breaking changes: ${report.breakingChanges} - - Added: ${report.added} - - Removed: ${report.removed} - - Modified: ${report.modified} - - Generated types are available as artifacts. - `; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); - } - - contract-snapshot: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Generate and snapshot types - run: | - npm run api:generate-spec - npm run api:generate-types - - - name: Commit generated types - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add apps/website/lib/types/generated/ - git diff --staged --quiet || git commit -m "chore: update generated API types [skip ci]" - git push \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 18de98416..d0a778429 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -npm test \ No newline at end of file +npx lint-staged \ No newline at end of file diff --git a/README.md b/README.md index 485d04579..3c5276704 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ npm test Individual applications support hot reload and watch mode during development: - **web-api**: Backend REST API server -- **web-client**: Frontend React application +- **web-client**: Frontend React application - **companion**: Desktop companion application ## Testing Commands @@ -64,12 +64,28 @@ Individual applications support hot reload and watch mode during development: GridPilot follows strict BDD (Behavior-Driven Development) with comprehensive test coverage. ### Local Verification Pipeline -Run this sequence before pushing to ensure correctness: -```bash -npm run lint && npm run typecheck && npm run test:unit && npm run test:integration -``` + +GridPilot uses **lint-staged** to automatically validate only changed files on commit: + +- `eslint --fix` runs on changed JS/TS/TSX files +- `vitest related --run` runs tests related to changed files +- `prettier --write` formats JSON, MD, and YAML files + +This ensures fast commits without running the full test suite. + +### Pre-Push Hook + +A **pre-push hook** runs the full verification pipeline before pushing to remote: + +- `npm run lint` - Check for linting errors +- `npm run typecheck` - Verify TypeScript types +- `npm run test:unit` - Run unit tests +- `npm run test:integration` - Run integration tests + +You can skip this with `git push --no-verify` if needed. ### Individual Commands + ```bash # Run all tests npm test @@ -147,4 +163,4 @@ Comprehensive documentation is available in the [`/docs`](docs/) directory: ## License -MIT License - see [LICENSE](LICENSE) file for details. \ No newline at end of file +MIT License - see [LICENSE](LICENSE) file for details. diff --git a/package.json b/package.json index 40fabd7c3..3cde0da43 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "glob": "^13.0.0", "husky": "^9.1.7", "jsdom": "^22.1.0", + "lint-staged": "^15.2.10", "openapi-typescript": "^7.4.3", "prettier": "^3.0.0", "puppeteer": "^24.31.0", @@ -128,6 +129,7 @@ "test:unit": "vitest run tests/unit", "test:watch": "vitest watch", "test:website:types": "vitest run --config vitest.website.config.ts apps/website/lib/types/contractConsumption.test.ts", + "verify": "npm run lint && npm run typecheck && npm run test:unit && npm run test:integration", "typecheck": "npm run typecheck:targets", "typecheck:grep": "npm run typescript", "typecheck:root": "npx tsc --noEmit --project tsconfig.json", @@ -139,10 +141,19 @@ "website:start": "npm run start --workspace=@gridpilot/website", "website:type-check": "npm run type-check --workspace=@gridpilot/website" }, + "lint-staged": { + "*.{js,ts,tsx}": [ + "eslint --fix", + "vitest related --run" + ], + "*.{json,md,yml}": [ + "prettier --write" + ] + }, "version": "0.1.0", "workspaces": [ "core/*", "apps/*", "testing/*" ] -} \ No newline at end of file +} diff --git a/plans/ci-optimization.md b/plans/ci-optimization.md new file mode 100644 index 000000000..59c12b967 --- /dev/null +++ b/plans/ci-optimization.md @@ -0,0 +1,100 @@ +# CI/CD & Dev Experience Optimization Plan + +## Current Situation + +- **Husky `pre-commit`**: Runs `npm test` (Vitest) on every commit. This likely runs the entire test suite, which is slow and frustrating for developers. +- **Gitea Actions**: Currently only has `contract-testing.yml`. +- **Missing**: No automated linting or type-checking in CI, no tiered testing strategy. + +## Proposed Strategy: The "Fast Feedback Loop" + +We will implement a tiered approach to balance speed and safety. + +### 1. Local Development (Husky + lint-staged) + +**Goal**: Prevent obvious errors from entering the repo without slowing down the dev. + +- **Trigger**: `pre-commit` +- **Action**: Only run on **staged files**. +- **Tasks**: + - `eslint --fix` + - `prettier --write` + - `vitest related` (only run tests related to changed files) + +### 2. Pull Request (Gitea Actions) + +**Goal**: Ensure the branch is stable and doesn't break the build or other modules. + +- **Trigger**: PR creation and updates. +- **Tasks**: + - Full `lint` + - Full `typecheck` (crucial for monorepo integrity) + - Full `unit tests` + - `integration tests` + - `contract tests` + +### 3. Merge to Main / Release (Gitea Actions) + +**Goal**: Final verification before deployment. + +- **Trigger**: Push to `main` or `develop`. +- **Tasks**: + - Everything from PR stage. + - `e2e tests` (Playwright) - these are the slowest and most expensive. + +--- + +## Implementation Steps + +### Step 1: Install and Configure `lint-staged` + +We need to add `lint-staged` to [`package.json`](package.json) and update the Husky hook. + +### Step 2: Optimize Husky Hook + +Update [`.husky/pre-commit`](.husky/pre-commit) to run `npx lint-staged` instead of `npm test`. + +### Step 3: Create Comprehensive CI Workflow + +Create `.github/workflows/ci.yml` (Gitea Actions compatible) to handle the heavy lifting. + +--- + +## Workflow Diagram + +```mermaid +graph TD + A[Developer Commits] --> B{Husky pre-commit} + B -->|lint-staged| C[Lint/Format Changed Files] + C --> D[Run Related Tests] + D --> E[Commit Success] + + E --> F[Push to PR] + F --> G{Gitea CI PR Job} + G --> H[Full Lint & Typecheck] + G --> I[Full Unit & Integration Tests] + G --> J[Contract Tests] + + J --> K{Merge to Main} + K --> L{Gitea CI Main Job} + L --> M[All PR Checks] + L --> N[Full E2E Tests] + N --> O[Deploy/Release] +``` + +## Proposed `lint-staged` Configuration + +```json +{ + "*.{js,ts,tsx}": ["eslint --fix", "vitest related --run"], + "*.{json,md,yml}": ["prettier --write"] +} +``` + +--- + +## Questions for the User + +1. Do you want to include `typecheck` in the `pre-commit` hook? (Note: `tsc` doesn't support linting only changed files easily, so it usually checks the whole project, which might be slow). +2. Should we run `integration tests` on every PR, or only on merge to `main`? +3. Are there specific directories that should be excluded from this automated flow?