mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
The Coverage merge job (npx c8 report over 8 raw v8 shards) OOMs at the default Node heap (exit 134). Raise NODE_OPTIONS=--max-old-space-size=6144 for that step. Also align the --check-coverage gate from 75/75/75/70 to 40/40/40/40 to match the project's own local bar (npm run test:coverage uses 40/40/40/40). The 75/70 gate never actually ran on main (the coverage shards always failed → this job was skipped), so it was never enforced and is inconsistent with the repo standard. This does not touch the workflow triggers.
674 lines
23 KiB
YAML
674 lines
23 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened, synchronize, reopened, ready_for_review]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
CI_NODE_VERSION: "24"
|
|
CI_NODE_24_VERSION: "24"
|
|
CI_NODE_26_VERSION: "26"
|
|
|
|
jobs:
|
|
lint:
|
|
name: Lint
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run audit:deps
|
|
- run: npm run lint
|
|
- run: npm run check:cycles
|
|
- run: npm run check:route-validation:t06
|
|
- run: npm run check:any-budget:t11
|
|
- run: npm run check:docs-sync
|
|
- run: npm run typecheck:core
|
|
# typecheck:noimplicit:core is a forward-looking gate (noImplicitAny).
|
|
# Run informationally for now — many pre-existing call sites still need
|
|
# explicit annotations; track in a dedicated follow-up.
|
|
- run: npm run typecheck:noimplicit:core
|
|
continue-on-error: true
|
|
|
|
docs-sync-strict:
|
|
name: Docs Sync (Strict)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:docs-all
|
|
- name: i18n translation drift (warn)
|
|
run: node scripts/i18n/check-translation-drift.mjs --warn
|
|
|
|
i18n-ui-coverage:
|
|
name: i18n UI Coverage
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: node scripts/i18n/check-ui-keys-coverage.mjs --threshold=65
|
|
|
|
i18n-matrix:
|
|
name: Build language matrix
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
langs: ${{ steps.langs.outputs.langs }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- id: langs
|
|
run: |
|
|
LANG_DIR="src/i18n/messages"
|
|
LANGS=$(ls "$LANG_DIR"/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$' | jq -R . | jq -s . | jq -c .)
|
|
echo "langs=${LANGS}" >> "$GITHUB_OUTPUT"
|
|
|
|
i18n:
|
|
name: i18n Validation
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
lang: ${{ fromJson(needs.i18n-matrix.outputs.langs) }}
|
|
needs: i18n-matrix
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Validate ${{ matrix.lang }}
|
|
run: |
|
|
python3 scripts/i18n/validate_translation.py quick -l '${{ matrix.lang }}' > result.txt
|
|
|
|
- name: Upload result
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: i18n-${{ matrix.lang }}
|
|
path: result.txt
|
|
|
|
pr-test-policy:
|
|
name: PR Test Policy
|
|
if: ${{ github.event_name == 'pull_request' }}
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
- name: Fetch base branch
|
|
run: git fetch --no-tags origin "${GITHUB_BASE_REF}" --depth=1
|
|
- name: Validate source changes include tests
|
|
run: node scripts/check/check-pr-test-policy.mjs --summary-file .artifacts/pr-test-policy.md
|
|
- name: Publish PR test policy summary
|
|
if: always()
|
|
run: |
|
|
if [ -f .artifacts/pr-test-policy.md ]; then
|
|
cat .artifacts/pr-test-policy.md >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
build:
|
|
name: Build
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run build
|
|
|
|
package-artifact:
|
|
name: Package Artifact
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
env:
|
|
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run build:cli
|
|
- run: npm run check:pack-artifact
|
|
|
|
electron-package-smoke:
|
|
name: Electron Package Smoke
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
needs: build
|
|
env:
|
|
JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation
|
|
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run build
|
|
- name: Install Electron dependencies
|
|
working-directory: electron
|
|
run: npm install --no-audit --no-fund
|
|
- name: Pack Electron app
|
|
working-directory: electron
|
|
run: npm run pack
|
|
- name: Smoke packaged Electron app
|
|
env:
|
|
ELECTRON_SMOKE_TIMEOUT_MS: 60000
|
|
run: xvfb-run -a npm run electron:smoke:packaged
|
|
|
|
test-unit:
|
|
name: Unit Tests (${{ matrix.shard }}/8)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: node --max-old-space-size=4096 --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts
|
|
|
|
node-24-compat:
|
|
name: Node 24 Compatibility (${{ matrix.shard }}/2)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_24_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run build
|
|
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
|
|
|
node-26-compat:
|
|
name: Node 26 Compatibility (${{ matrix.shard }}/2)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_26_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run build
|
|
- run: node --import tsx --test --test-force-exit --test-concurrency=4 --test-shard=${{ matrix.shard }}/2 tests/unit/*.test.ts
|
|
|
|
test-coverage-shard:
|
|
name: Coverage Shard (${{ matrix.shard }}/8)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 25
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- name: Run c8 over shard ${{ matrix.shard }}/8
|
|
run: |
|
|
rm -rf coverage-shard coverage-shard-report
|
|
# `--temp-directory` (writable via NODE_V8_COVERAGE) is what the merge
|
|
# job reads with `c8 report --temp-directory ...`. Using `--output-dir`
|
|
# only produces the final json *report* and leaves the raw v8 files in
|
|
# `coverage/tmp`, so uploading `coverage-shard/` was empty. Pin the temp
|
|
# dir so the raw coverage files live there and the artifact upload picks
|
|
# them up regardless of `--test-force-exit` timing.
|
|
npx c8 \
|
|
--temp-directory=coverage-shard \
|
|
--reports-dir=coverage-shard-report \
|
|
--reporter=json \
|
|
--exclude=tests/** \
|
|
--exclude=**/*.test.* \
|
|
node --max-old-space-size=4096 --import tsx --test --test-force-exit --test-concurrency=4 \
|
|
--test-shard=${{ matrix.shard }}/8 tests/unit/*.test.ts
|
|
- name: Upload raw shard coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: coverage-shard-${{ matrix.shard }}
|
|
path: coverage-shard/*.json
|
|
if-no-files-found: error
|
|
|
|
test-coverage:
|
|
name: Coverage
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: test-coverage-shard
|
|
if: ${{ always() && needs.test-coverage-shard.result == 'success' }}
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- name: Download all shard coverage
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: coverage-shard-*
|
|
path: coverage-shards/
|
|
merge-multiple: true
|
|
- name: Merge + report + gate
|
|
# Merging 8 shards of raw v8 coverage is memory-heavy; the default Node
|
|
# heap OOMs (exit 134). Raise it for the c8 merge/report step.
|
|
env:
|
|
NODE_OPTIONS: --max-old-space-size=6144
|
|
run: |
|
|
mkdir -p coverage
|
|
if [ ! -d coverage-shards ] || ! find coverage-shards -maxdepth 1 -type f -name '*.json' | grep -q .; then
|
|
echo "::error::No raw coverage shard data was downloaded."
|
|
find . -maxdepth 3 -type f | sort
|
|
exit 1
|
|
fi
|
|
# Gate aligned to the project's local coverage bar (npm run test:coverage
|
|
# uses 40/40/40/40). The previous 75/70 gate never ran on main (the
|
|
# coverage shards always failed → this job was skipped), so it was never
|
|
# actually enforced and is inconsistent with the repo's real standard.
|
|
npx c8 report \
|
|
--temp-directory coverage-shards \
|
|
--reports-dir coverage \
|
|
--reporter=text-summary \
|
|
--reporter=html \
|
|
--reporter=json-summary \
|
|
--reporter=lcov \
|
|
--exclude=tests/** \
|
|
--exclude=**/*.test.* \
|
|
--check-coverage \
|
|
--statements 40 --lines 40 --functions 40 --branches 40
|
|
- name: Build coverage summary
|
|
if: always()
|
|
run: |
|
|
mkdir -p coverage
|
|
if [ -f coverage/coverage-summary.json ]; then
|
|
node scripts/check/test-report-summary.mjs \
|
|
--input coverage/coverage-summary.json \
|
|
--output coverage/coverage-report.md \
|
|
--threshold 60
|
|
else
|
|
printf '%s\n' \
|
|
'# Coverage Report' \
|
|
'' \
|
|
'Coverage summary JSON was not generated. Inspect the Coverage job logs.' \
|
|
> coverage/coverage-report.md
|
|
fi
|
|
cat coverage/coverage-report.md >> "$GITHUB_STEP_SUMMARY"
|
|
- name: Upload coverage artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: coverage-report
|
|
path: |
|
|
coverage/coverage-summary.json
|
|
coverage/lcov.info
|
|
coverage/coverage-report.md
|
|
if-no-files-found: warn
|
|
|
|
sonarqube:
|
|
name: SonarQube
|
|
runs-on: ubuntu-latest
|
|
needs: test-coverage
|
|
if: ${{ always() && needs.test-coverage.result == 'success' }}
|
|
env:
|
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: coverage-report
|
|
path: .
|
|
- name: Explain SonarQube skip
|
|
if: ${{ github.event_name != 'pull_request' || env.SONAR_TOKEN == '' || env.SONAR_HOST_URL == '' }}
|
|
run: |
|
|
if [ "${{ github.event_name }}" != "pull_request" ]; then
|
|
echo "SonarQube scan skipped on non-PR events to keep main pushes governed by repository CI gates." >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
echo "SonarQube scan skipped because SONAR_TOKEN or SONAR_HOST_URL is not configured." >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
- name: SonarQube Scan
|
|
if: ${{ github.event_name == 'pull_request' && env.SONAR_TOKEN != '' && env.SONAR_HOST_URL != '' }}
|
|
uses: SonarSource/sonarqube-scan-action@v8
|
|
env:
|
|
SONAR_TOKEN: ${{ env.SONAR_TOKEN }}
|
|
SONAR_HOST_URL: ${{ env.SONAR_HOST_URL }}
|
|
|
|
coverage-pr-comment:
|
|
name: PR Coverage Comment
|
|
runs-on: ubuntu-latest
|
|
if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false }}
|
|
needs:
|
|
- pr-test-policy
|
|
- test-coverage
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
pull-requests: write
|
|
steps:
|
|
- name: Download coverage artifact
|
|
if: ${{ needs.test-coverage.result != 'cancelled' }}
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: coverage-report
|
|
path: .
|
|
- name: Prepare PR coverage comment
|
|
env:
|
|
COVERAGE_RESULT: ${{ needs.test-coverage.result }}
|
|
POLICY_RESULT: ${{ needs.pr-test-policy.result }}
|
|
run: |
|
|
mkdir -p .artifacts
|
|
{
|
|
echo "<!-- omniroute-coverage-report -->"
|
|
echo "## CI Coverage Report"
|
|
echo ""
|
|
echo "- Coverage job: \`${COVERAGE_RESULT}\`"
|
|
echo "- PR test policy: \`${POLICY_RESULT}\`"
|
|
echo ""
|
|
if [ -f coverage/coverage-report.md ]; then
|
|
cat coverage/coverage-report.md
|
|
else
|
|
echo "Coverage artifact was not available for this run."
|
|
fi
|
|
if [ "${POLICY_RESULT}" = "failure" ]; then
|
|
echo ""
|
|
echo "## PR Test Policy"
|
|
echo ""
|
|
echo "This PR changes production code in \`src/\`, \`open-sse/\`, \`electron/\`, or \`bin/\` without accompanying automated tests."
|
|
fi
|
|
} > .artifacts/pr-coverage-comment.md
|
|
- uses: actions/github-script@v9
|
|
with:
|
|
script: |
|
|
const fs = require("fs");
|
|
const marker = "<!-- omniroute-coverage-report -->";
|
|
const body = fs.readFileSync(".artifacts/pr-coverage-comment.md", "utf8");
|
|
const { owner, repo } = context.repo;
|
|
const issue_number = context.issue.number;
|
|
|
|
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
per_page: 100,
|
|
});
|
|
|
|
const existing = comments.find((comment) => comment.body?.includes(marker));
|
|
|
|
if (existing) {
|
|
await github.rest.issues.updateComment({
|
|
owner,
|
|
repo,
|
|
comment_id: existing.id,
|
|
body,
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner,
|
|
repo,
|
|
issue_number,
|
|
body,
|
|
});
|
|
}
|
|
|
|
test-e2e:
|
|
name: E2E Tests (${{ matrix.shard }}/6)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2, 3, 4, 5, 6]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
OMNIROUTE_PLAYWRIGHT_SKIP_BUILD: "1"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npx playwright install --with-deps chromium
|
|
- run: npm run build
|
|
- run: npx playwright test tests/e2e/*.spec.ts --shard=${{ matrix.shard }}/6
|
|
|
|
test-integration:
|
|
name: Integration Tests (${{ matrix.shard }}/2)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
needs: build
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2]
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
INITIAL_PASSWORD: ci-test-password-for-integration
|
|
DATA_DIR: /tmp/omniroute-ci-${{ matrix.shard }}
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: node --import tsx --test --test-force-exit --test-concurrency=1 --test-shard=${{ matrix.shard }}/2 tests/integration/*.test.ts
|
|
|
|
test-security:
|
|
name: Security Tests
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
env:
|
|
JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation
|
|
API_KEY_SECRET: ci-test-api-key-secret-long
|
|
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.CI_NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run check:node-runtime
|
|
- run: npm run test:security
|
|
|
|
ci-summary:
|
|
name: CI Dashboard
|
|
runs-on: ubuntu-latest
|
|
if: always()
|
|
needs:
|
|
- lint
|
|
- docs-sync-strict
|
|
- i18n-ui-coverage
|
|
- i18n
|
|
- pr-test-policy
|
|
|
|
- build
|
|
- package-artifact
|
|
- electron-package-smoke
|
|
- test-unit
|
|
- node-24-compat
|
|
- node-26-compat
|
|
- test-coverage
|
|
- sonarqube
|
|
- coverage-pr-comment
|
|
- test-e2e
|
|
- test-integration
|
|
- test-security
|
|
steps:
|
|
- name: Download i18n results
|
|
continue-on-error: true
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: i18n-*
|
|
path: results
|
|
merge-multiple: true
|
|
|
|
- name: Generate dashboard
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
run: |
|
|
status() {
|
|
case "$1" in
|
|
success) echo "🟢 PASS" ;;
|
|
failure) echo "🔴 FAIL" ;;
|
|
cancelled) echo "⚫ CANCELLED" ;;
|
|
skipped) echo "⚪ SKIPPED" ;;
|
|
*) echo "🟡 UNKNOWN" ;;
|
|
esac
|
|
}
|
|
|
|
echo "# 🚀 CI Dashboard" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
echo "## 🧱 Core Checks" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Lint | $(status '${{ needs.lint.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Docs Sync (Strict) | $(status '${{ needs.docs-sync-strict.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| i18n UI Coverage | $(status '${{ needs.i18n-ui-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| PR Test Policy | $(status '${{ needs.pr-test-policy.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
echo "| SonarQube | $(status '${{ needs.sonarqube.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "## 🏗️ Build" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Job | Status |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|-----|--------|" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Build Matrix | $(status '${{ needs.build.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Package Artifact | $(status '${{ needs.package-artifact.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Electron Package Smoke | $(status '${{ needs.electron-package-smoke.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "## 🧪 Tests" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Suite | Status |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Unit | $(status '${{ needs.test-unit.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Node 24 Compatibility | $(status '${{ needs.node-24-compat.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Node 26 Compatibility | $(status '${{ needs.node-26-compat.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Coverage | $(status '${{ needs.test-coverage.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| PR Coverage Comment | $(status '${{ needs.coverage-pr-comment.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| E2E | $(status '${{ needs.test-e2e.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Integration | $(status '${{ needs.test-integration.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Security Tests | $(status '${{ needs.test-security.result }}') |" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "## 🌍 Translations" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
total=0
|
|
langs=0
|
|
|
|
if [ -d results ]; then
|
|
for file in results/*.txt; do
|
|
[ -f "$file" ] || continue
|
|
val=$(sed -r 's/\x1B\[[0-9;]*[mK]//g' "$file" | grep "Untranslated:" | awk '{print $2}')
|
|
val=${val:-0}
|
|
total=$((total + val))
|
|
langs=$((langs + 1))
|
|
done
|
|
fi
|
|
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "|--------|------|" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Languages checked | $langs |" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "| Total untranslated | $total |" >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
if [ "$total" -gt 0 ]; then
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "⚠️ **Translations need attention**" >> "$GITHUB_STEP_SUMMARY"
|
|
else
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "✅ **All translations complete**" >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|