feat: ship v8.93 signed Compose blueprint catalog #646
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| test-and-validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Check for critical vulnerabilities (production deps only) | |
| # npm's retired Quick Audit fallback can reject a valid lockfile when the | |
| # Bulk Advisory endpoint returns invalid data. Use Google's first-party | |
| # OSV scanner instead, pinned and checksum-verified for reproducibility. | |
| # The validator preserves the existing policy: dev-only findings are | |
| # excluded and only production findings with CVSS >= 9.0 fail the build. | |
| env: | |
| OSV_VERSION: '2.3.8' | |
| OSV_LINUX_AMD64_SHA256: 'bc98e15319ed0d515e3f9235287ba53cdc5535d576d24fd573978ecfe9ab92dc' | |
| run: | | |
| curl --fail --silent --show-error --location \ | |
| "https://github.com/google/osv-scanner/releases/download/v${OSV_VERSION}/osv-scanner_linux_amd64" \ | |
| --output /tmp/osv-scanner | |
| echo "${OSV_LINUX_AMD64_SHA256} /tmp/osv-scanner" | sha256sum --check --strict | |
| chmod +x /tmp/osv-scanner | |
| set +e | |
| /tmp/osv-scanner scan source \ | |
| --lockfile package-lock.json \ | |
| --format json \ | |
| --verbosity error \ | |
| --output-file /tmp/osv-results.json | |
| SCAN_STATUS=$? | |
| set -e | |
| # OSV returns 1 when it found vulnerabilities. Other non-zero exit | |
| # codes represent scanner/input failures and must not be suppressed. | |
| if [ "$SCAN_STATUS" -ne 0 ] && [ "$SCAN_STATUS" -ne 1 ]; then | |
| exit "$SCAN_STATUS" | |
| fi | |
| node scripts/check-osv-results.js /tmp/osv-results.json 9 | |
| - name: Validate JavaScript syntax (backend) | |
| run: | | |
| echo "Checking backend JS files..." | |
| find src -name '*.js' -not -path '*__tests__*' -exec node --check {} \; | |
| echo "All backend files OK" | |
| - name: Validate JavaScript syntax (frontend) | |
| run: | | |
| echo "Checking frontend JS files..." | |
| for f in public/js/*.js public/js/pages/*.js public/js/components/*.js public/js/i18n/*.js; do | |
| node --check "$f" || exit 1 | |
| done | |
| echo "All frontend files OK" | |
| - name: Lint (ESLint) | |
| # v7.7.0: enforce 0 warnings / 0 errors. CONTRIBUTING.md tells contributors | |
| # `npm run lint` must be clean — without this step, that promise was on the honor system. | |
| run: npm run lint | |
| - name: Run tests | |
| id: tests | |
| run: | | |
| # Capture Jest output, extract counts, and expose as step outputs so | |
| # the Summary step below can report accurate numbers instead of a | |
| # hardcoded string that rotted across releases pre-v6.15.0. | |
| set -o pipefail | |
| npm test 2>&1 | tee /tmp/jest.out | |
| TEST_LINE=$(grep -oE 'Tests:\s+.*(passed|skipped|failed)' /tmp/jest.out | tail -1 || echo "") | |
| PASSED=$(echo "$TEST_LINE" | grep -oE '[0-9]+ passed' | head -1 | grep -oE '[0-9]+' || echo "0") | |
| SKIPPED=$(echo "$TEST_LINE" | grep -oE '[0-9]+ skipped' | head -1 | grep -oE '[0-9]+' || echo "0") | |
| echo "passed=$PASSED" >> "$GITHUB_OUTPUT" | |
| echo "skipped=$SKIPPED" >> "$GITHUB_OUTPUT" | |
| env: | |
| APP_SECRET: ci-test-secret-key-not-for-production | |
| ENCRYPTION_KEY: ci-test-encryption-key-32-chars-min | |
| APP_ENV: test | |
| # v6.9.2: live Cloudflare smoke test. Skipped when secret absent. | |
| # Provision: Repo Settings → Secrets → Actions → CLOUDFLARE_TEST_TOKEN | |
| # (scoped token, `User:Read` only — no Zone or DNS permissions needed). | |
| CLOUDFLARE_TEST_TOKEN: ${{ secrets.CLOUDFLARE_TEST_TOKEN }} | |
| - name: Validate i18n completeness | |
| run: | | |
| node -e " | |
| global.window = {}; | |
| global.localStorage = { getItem: ()=>null, setItem: ()=>{} }; | |
| eval(require('fs').readFileSync('public/js/i18n.js', 'utf8')); | |
| const i18n = window.i18n; | |
| const langFiles = require('fs').readdirSync('public/js/i18n') | |
| .filter(f => f.endsWith('.js') && f !== 'TEMPLATE.js'); | |
| for (const f of langFiles) { | |
| eval(require('fs').readFileSync('public/js/i18n/' + f, 'utf8')); | |
| } | |
| console.log('Languages:', i18n.languages.map(l => l.code).join(', ')); | |
| const enKeys = JSON.stringify(i18n._translations.en).match(/\"[^\"]+\":/g)?.length || 0; | |
| console.log('EN keys: ~' + enKeys); | |
| if (enKeys < 100) { console.error('ERROR: EN translation seems incomplete'); process.exit(1); } | |
| console.log('i18n validation passed'); | |
| " | |
| - name: Validate self-service localization safety copy | |
| run: npm run check:self-service-i18n | |
| - name: Validate self-service accessibility contract | |
| run: npm run check:self-service-a11y | |
| - name: Validate virtualization research registry | |
| run: npm run check:virtualization-research | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "### CI Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- Node.js syntax: ✅ (backend + frontend)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Lint (ESLint): ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Tests: ✅ (${{ steps.tests.outputs.passed || 'unknown' }} passed, ${{ steps.tests.outputs.skipped || '0' }} skipped — 100% passing)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Security audit: ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- i18n: ✅ (11 languages)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Dependencies: installed" >> $GITHUB_STEP_SUMMARY |