name: CI on: push: branches: [ main ] pull_request: branches: [ main ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint: runs-on: self-hosted name: Lint Code steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Run ESLint run: npm run lint type-check: runs-on: self-hosted name: TypeScript Type Check steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Run TypeScript compiler run: npx tsc --noEmit test: runs-on: self-hosted name: Run Tests (Node ${{ matrix.node-version }}) strategy: matrix: node-version: [18, 20] steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Run npm tests run: npm test coverage: runs-on: self-hosted name: Test Coverage steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Run tests with coverage run: npm run test:coverage - name: Upload coverage reports uses: https://code.forgejo.org/forgejo/upload-artifact@v4 with: name: coverage-report.zip path: coverage/ build: runs-on: self-hosted name: Build Project steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Build TypeScript run: npm run build:ts - name: Build Frontend run: npm run build:frontend - name: Build CSS run: npm run build-css - name: Verify build outputs run: | echo "Checking backend build..." test -f dist/server.js || exit 1 echo "Checking frontend build..." test -f public/dist/app-main.js || exit 1 test -f public/dist/app-admin.js || exit 1 test -f public/dist/app-privacy.js || exit 1 echo "Checking CSS build..." test -f public/style.css || exit 1 echo "✅ All build outputs verified!" security: runs-on: self-hosted name: Security Checks steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Install dependencies run: npm ci - name: Run npm audit run: npm audit --audit-level=high continue-on-error: true - name: Check for secrets run: | echo "Checking for potential secrets..." # Check for hardcoded Mapbox tokens (pk. or sk. prefixes) if find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -v dist | grep -v .git | xargs grep -E "(pk\.|sk\.)[a-zA-Z0-9]{50,}" > /dev/null 2>&1; then echo "❌ Found hardcoded Mapbox token!" find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -v dist | grep -v .git | xargs grep -E "(pk\.|sk\.)[a-zA-Z0-9]{50,}" exit 1 fi # Check for hardcoded admin passwords (exclude test files and obvious fallbacks) if find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -v dist | grep -v .git | grep -v tests | xargs grep -E "ADMIN_PASSWORD.*=.*['\"][^'\"]{8,}['\"]" | grep -v "admin123" | grep -v "test_" > /dev/null 2>&1; then echo "❌ Found hardcoded admin password!" find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -v dist | grep -v .git | grep -v tests | xargs grep -E "ADMIN_PASSWORD.*=.*['\"][^'\"]{8,}['\"]" | grep -v "admin123" | grep -v "test_" exit 1 fi echo "✅ No hardcoded secrets found" validate-i18n: runs-on: self-hosted name: Validate i18n Files steps: - name: Checkout code uses: https://code.forgejo.org/actions/checkout@v4 - name: Setup Node.js run: | node --version npm --version - name: Validate JSON files run: | echo "Validating i18n JSON files..." for file in src/i18n/locales/*.json; do echo "Checking $file..." node -e "JSON.parse(require('fs').readFileSync('$file', 'utf8'))" || exit 1 done echo "✅ All i18n files are valid JSON" - name: Check translation keys match run: | echo "Comparing translation keys..." node -e " const fs = require('fs'); const en = JSON.parse(fs.readFileSync('src/i18n/locales/en.json', 'utf8')); const esMX = JSON.parse(fs.readFileSync('src/i18n/locales/es-MX.json', 'utf8')); function getKeys(obj, prefix = '') { let keys = []; for (const key in obj) { const fullKey = prefix ? prefix + '.' + key : key; if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { keys = keys.concat(getKeys(obj[key], fullKey)); } else { keys.push(fullKey); } } return keys.sort(); } const enKeys = getKeys(en); const esMXKeys = getKeys(esMX); const missingInEs = enKeys.filter(k => !esMXKeys.includes(k)); const missingInEn = esMXKeys.filter(k => !enKeys.includes(k)); if (missingInEs.length > 0) { console.error('❌ Keys in en.json missing from es-MX.json:', missingInEs); process.exit(1); } if (missingInEn.length > 0) { console.error('❌ Keys in es-MX.json missing from en.json:', missingInEn); process.exit(1); } console.log('✅ All translation keys match between locales'); "