diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0ea7af31..77815404 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -109,6 +109,41 @@ updates: - "dependencies" - "rust" + - package-ecosystem: "npm" + directory: "/workers" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 3 + labels: + - "dependencies" + - "workers" + versioning-strategy: "increase" + + - package-ecosystem: "npm" + directory: "/sdks" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 3 + labels: + - "dependencies" + - "sdks" + versioning-strategy: "increase" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "09:00" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + # Enable security updates security-updates: - package-ecosystem: "npm" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..6ecf0383 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,41 @@ +name: Coverage + +on: + pull_request: + paths: + - 'frontend/**' + - '!frontend/e2e/**' + push: + branches: + - main + +jobs: + coverage: + name: Code coverage with threshold enforcement + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests with coverage + run: npm run test -- --coverage + env: + NEXT_TELEMETRY_DISABLED: '1' + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: frontend/coverage + flags: frontend + fail_ci_if_error: false diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml new file mode 100644 index 00000000..cd5e280f --- /dev/null +++ b/.github/workflows/size-limit.yml @@ -0,0 +1,54 @@ +name: Size Limit + +on: + pull_request: + paths: + - 'frontend/app/**' + - 'frontend/components/**' + - 'frontend/lib/**' + - 'frontend/next.config.ts' + - 'frontend/bundle-budget.json' + - 'frontend/package.json' + - '.size-limit.json' + push: + branches: + - main + paths: + - 'frontend/app/**' + - 'frontend/components/**' + - 'frontend/next.config.ts' + +jobs: + size-limit: + name: Bundle size budgets and regression alerts + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + NEXT_TELEMETRY_DISABLED: '1' + SIZE_LIMIT: 'true' + + - name: Enforce bundle budgets + run: npm run bundle:check + + - name: Report size regressions on pull requests + if: github.event_name == 'pull_request' + uses: andresz1/size-limit-action@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + directory: frontend + config_path: .size-limit.json diff --git a/.size-limit.json b/.size-limit.json new file mode 100644 index 00000000..46ae284e --- /dev/null +++ b/.size-limit.json @@ -0,0 +1,27 @@ +{ + "name": "agenticpay", + "repository": "https://github.com/Smartdevs17/agenticpay", + "sizeLimit": [ + { + "name": "frontend framework chunk (gzip)", + "path": "frontend/.next/static/chunks/framework-*.js", + "limit": "210 kB", + "gzip": true, + "running": false + }, + { + "name": "frontend main app chunks (gzip)", + "path": "frontend/.next/static/chunks/*.js", + "limit": "1500 kB", + "gzip": true, + "running": false + }, + { + "name": "frontend css (gzip)", + "path": "frontend/.next/static/css/*.css", + "limit": "200 kB", + "gzip": true, + "running": false + } + ] +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..2ebfaa40 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,35 @@ +coverage: + status: + project: + default: + target: auto + threshold: 1% + flags: + - frontend + informational: false + patch: + default: + target: 80% + threshold: 1% + informational: false + ignore: + - "frontend/e2e/**" + - "frontend/.next/**" + - "frontend/playwright-report/**" + - "frontend/test-results/**" + - "**/*.config.*" + - "**/*.d.ts" + +comment: + layout: "reach, diff, flags, files" + behavior: default + require_changes: false + threshold: 1% + flags: + - frontend + +github_checks: + annotations: true + +fixes: + - "frontend/::" diff --git a/docker-compose.yml b/docker-compose.yml index a71870e2..acdcce61 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,6 +60,58 @@ services: depends_on: - loki + backend: + image: node:20-alpine + working_dir: /app + command: sh -c "npm install && npm run dev" + ports: + - '4000:4000' + environment: + NODE_ENV: development + DATABASE_URL: postgres://postgres:postgres@postgres:5432/agenticpay + REDIS_URL: redis://redis:6379 + LOG_API_URL: http://loki:3100 + volumes: + - ./backend:/app + - /app/node_modules + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + develop: + watch: + - action: sync + path: ./backend + target: /app + - action: rebuild + path: ./backend/package.json + + frontend: + image: node:20-alpine + working_dir: /app + command: sh -c "npm install && npm run dev" + ports: + - '3000:3000' + environment: + NODE_ENV: development + NEXT_PUBLIC_API_URL: http://backend:4000 + NEXT_PUBLIC_CDN_URL: '' + NEXT_TELEMETRY_DISABLED: '1' + volumes: + - ./frontend:/app + - /app/node_modules + - /app/.next + depends_on: + - backend + develop: + watch: + - action: sync + path: ./frontend + target: /app + - action: rebuild + path: ./frontend/package.json + volumes: agenticpay-postgres: agenticpay-redis: diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 74d52260..94a09a15 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -168,6 +168,28 @@ const nextConfig: NextConfig = { }; } + if (process.env.SIZE_LIMIT === "true" && !isServer) { + config.plugins = config.plugins || []; + config.plugins.push({ + apply(compiler: any) { + compiler.hooks.done.tap("SizeLimitStats", (stats: any) => { + const fs = require("fs"); + const path = require("path"); + const out = stats.toJson({ + all: false, + assets: true, + chunks: true, + chunkGroups: true, + }); + fs.writeFileSync( + path.join(process.cwd(), ".next", "size-limit-stats.json"), + JSON.stringify(out), + ); + }); + }, + } as any); + } + return config; }, images: { diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index b0cb96cd..c6d378a0 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -18,7 +18,26 @@ export default defineConfig({ exclude: ['node_modules/**', '.next/**', 'e2e/**', 'playwright-report/**', 'test-results/**'], coverage: { provider: 'v8', - reporter: ['text', 'json', 'html'], + reporter: ['text', 'json', 'html', 'lcov'], + reportsDirectory: './coverage', + exclude: [ + 'node_modules/**', + '.next/**', + 'e2e/**', + 'playwright-report/**', + 'test-results/**', + '**/*.config.*', + '**/*.d.ts', + '**/*.test.{ts,tsx}', + '**/vitest.setup.ts', + ], + thresholds: { + statements: 80, + branches: 80, + functions: 80, + lines: 80, + }, + skipFull: true, }, }, }) diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..cf3fed39 --- /dev/null +++ b/renovate.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":semanticCommits", + ":preserveSemverRanges", + "schedule:weekly", + "group:allNonMajor" + ], + "labels": ["dependencies", "renovate"], + "assignees": ["mmesolove"], + "reviewers": ["mmesolove"], + "separateMajorMinor": true, + "ignoreMajor": false, + "packageRules": [ + { + "matchUpdateTypes": ["major"], + "labels": ["dependencies", "major"] + }, + { + "matchDepTypes": ["devDependencies"], + "labels": ["dependencies", "dev"] + } + ], + "npm": { + "enabled": true + }, + "lockFileMaintenance": { + "enabled": true, + "schedule": "before 9am on monday" + }, + "postUpdateOptions": ["npmDedupe"], + "prConcurrentLimit": 10, + "prHourlyLimit": 2, + "timezone": "UTC", + "enabledManagers": ["npm", "github-actions"], + "baseBranches": ["main"] +} diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 00000000..37d0c5fa --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$SCRIPT_DIR" + +COMPOSE_FILES=("docker-compose.yml") +if [ -f "docker-compose.sandbox.yml" ]; then + COMPOSE_FILES+=("docker-compose.sandbox.yml") +fi + +COMPOSE_ARGS=() +for f in "${COMPOSE_FILES[@]}"; do + COMPOSE_ARGS+=(-f "$f") +done + +usage() { + cat <<'EOF' +Dev environment orchestrator + +Usage: + scripts/dev.sh up Start infra + backend + frontend with hot reload + scripts/dev.sh down Stop all services + scripts/dev.sh logs [svc] Tail logs (optionally for a single service) + scripts/dev.sh ps Show service status + scripts/dev.sh restart [svc] Restart one or all services + scripts/dev.sh seed Run the sandbox seed script + scripts/dev.sh watch Start infra then stream all logs +EOF +} + +cmd="${1:-up}" +shift || true + +case "$cmd" in + up) + docker compose "${COMPOSE_ARGS[@]}" up -d --build + echo "Dev environment is up. Frontend: http://localhost:3000 Backend: http://localhost:4000" + ;; + down) + docker compose "${COMPOSE_ARGS[@]}" down + ;; + logs) + docker compose "${COMPOSE_ARGS[@]}" logs -f "${1:-}" + ;; + watch) + docker compose "${COMPOSE_ARGS[@]}" up -d --build + docker compose "${COMPOSE_ARGS[@]}" logs -f + ;; + ps) + docker compose "${COMPOSE_ARGS[@]}" ps + ;; + restart) + if [ -n "${1:-}" ]; then + docker compose "${COMPOSE_ARGS[@]}" restart "$1" + else + docker compose "${COMPOSE_ARGS[@]}" restart + fi + ;; + seed) + docker compose "${COMPOSE_ARGS[@]}" exec backend node /app/scripts/seed.js + ;; + *) + usage + exit 1 + ;; +esac