From 4d4479ff47ad1b7f4429de81296e88c5ce550d36 Mon Sep 17 00:00:00 2001 From: jerry609 <1772030600@qq.com> Date: Sun, 15 Mar 2026 13:10:49 +0800 Subject: [PATCH 1/4] feat: add SonarCloud CI workflow --- .github/workflows/ci.yml | 31 +- .github/workflows/sonarcloud.yml | 121 +++++ .gitignore | 5 + pyproject.toml | 8 + requirements-ci.txt | 1 + scripts/ci/run_backend_offline_gates.sh | 36 ++ sonar-project.properties | 12 + web/package-lock.json | 674 ++++++++++++++++++++++++ web/package.json | 2 + web/vitest.config.ts | 19 +- 10 files changed, 878 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/sonarcloud.yml create mode 100644 scripts/ci/run_backend_offline_gates.sh create mode 100644 sonar-project.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca47382f..87f07e9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,36 +44,7 @@ jobs: env: PYTHONPATH: src PAPERBOT_DB_URL: sqlite:///data/paperbot-ci.db - run: | - python -m pytest -q \ - tests/unit/test_scholar_from_config.py \ - tests/unit/test_source_registry_modes.py \ - tests/unit/test_arq_worker_settings.py \ - tests/unit/test_jobs_routes_import.py \ - tests/unit/test_dailypaper.py \ - tests/unit/test_paper_judge.py \ - tests/unit/test_memory_module.py \ - tests/unit/test_memory_metric_collector.py \ - tests/unit/test_llm_service.py \ - tests/unit/test_di_container.py \ - tests/unit/test_pipeline.py \ - tests/unit/repro/test_agents.py \ - tests/unit/repro/test_blueprint.py \ - tests/unit/repro/test_memory.py \ - tests/unit/repro/test_orchestrator.py \ - tests/unit/repro/test_rag.py \ - tests/integration/test_eventlog_sqlalchemy.py \ - tests/integration/test_crawler_contract_parsers.py \ - tests/integration/test_arxiv_connector_fixture.py \ - tests/integration/test_reddit_connector_fixture.py \ - tests/integration/test_x_importer_fixture.py \ - tests/integration/test_repro_deepcode.py \ - tests/test_generation_agent.py \ - tests/test_repro_planning.py \ - tests/test_repro_agent.py \ - tests/test_repro_e2e.py \ - tests/test_repro_models.py \ - tests/e2e/test_api_track_fullstack_offline.py + run: bash scripts/ci/run_backend_offline_gates.sh - name: Run eval smokes env: diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..38081073 --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,121 @@ +name: sonarcloud + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - master + - dev + workflow_dispatch: + +concurrency: + group: sonarcloud-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: read + +jobs: + scan: + name: SonarCloud Scan + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + PYTHONPATH: src + PAPERBOT_DB_URL: sqlite:///data/paperbot-ci.db + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate SonarCloud settings + shell: bash + env: + SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} + SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + set -euo pipefail + test -n "${SONAR_ORGANIZATION}" || { echo "Missing repository variable SONAR_ORGANIZATION"; exit 1; } + test -n "${SONAR_PROJECT_KEY}" || { echo "Missing repository variable SONAR_PROJECT_KEY"; exit 1; } + test -n "${SONAR_TOKEN}" || { echo "Missing repository secret SONAR_TOKEN"; exit 1; } + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-sonar-pip-${{ hashFiles('requirements-ci.txt') }} + restore-keys: | + ${{ runner.os }}-sonar-pip- + + - name: Install backend dependencies + run: | + python -m pip install -U pip + python -m pip install -r requirements-ci.txt + + - name: Run backend tests with coverage + run: bash scripts/ci/run_backend_offline_gates.sh --cov=src --cov-report=xml:coverage.xml + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install web dependencies + working-directory: web + run: npm ci + + - name: Run web tests with coverage + working-directory: web + run: npm run test:coverage + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar-cache + restore-keys: | + ${{ runner.os }}-sonar-cache + + - name: SonarCloud scan + uses: SonarSource/sonarqube-scan-action@v7 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + args: > + -Dsonar.organization=${{ vars.SONAR_ORGANIZATION }} + -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} + -Dsonar.qualitygate.wait=true + -Dsonar.qualitygate.timeout=300 + + fork_notice: + name: Fork PR notice + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork + runs-on: ubuntu-latest + steps: + - name: Record skip reason for fork PR + shell: bash + run: | + set -euo pipefail + echo "Fork PR detected; SonarCloud scan is skipped because GitHub Actions does not expose repository secrets to workflows from forks." + { + echo "### Fork PR SonarCloud scan skipped" + echo + echo "This pull request comes from a fork, so GitHub Actions cannot access the repository secrets needed for SonarCloud analysis." + echo + echo "Run the scan on a branch inside the main repository if you need PR decoration before merge." + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/.gitignore b/.gitignore index d446baa0..a01f617c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ Thumbs.db logs/ /data/ /papers/ +.coverage +coverage.xml +.scannerwork/ debug_*.txt debug_*.json response.html @@ -54,6 +57,8 @@ reports/ # Node.js node_modules/ +coverage/ +web/coverage/ cli/dist/ npm-debug.log diff --git a/pyproject.toml b/pyproject.toml index 9e9590b6..2b8598f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "pytest>=7.0.0", + "pytest-cov>=5.0.0", "pytest-asyncio>=0.21.0", "pytest-mock>=3.12.0", "black>=23.0.0", @@ -125,6 +126,13 @@ live_mode = true testpaths = ["tests"] asyncio_mode = "strict" +[tool.coverage.run] +relative_files = true +source = ["src"] + +[tool.coverage.report] +omit = ["tests/*"] + [tool.black] line-length = 100 target-version = ['py310'] diff --git a/requirements-ci.txt b/requirements-ci.txt index 0eeb3c8f..52f500be 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -19,6 +19,7 @@ SQLAlchemy>=2.0.0 alembic>=1.13.0 pytest>=7.2.0 +pytest-cov>=5.0.0 pytest-asyncio>=0.21.0 pytest-mock>=3.12.0 diff --git a/scripts/ci/run_backend_offline_gates.sh b/scripts/ci/run_backend_offline_gates.sh new file mode 100644 index 00000000..4d8812ea --- /dev/null +++ b/scripts/ci/run_backend_offline_gates.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/../.." + +export PYTHONPATH="${PYTHONPATH:-src}" + +python -m pytest -q "$@" \ + tests/unit/test_scholar_from_config.py \ + tests/unit/test_source_registry_modes.py \ + tests/unit/test_arq_worker_settings.py \ + tests/unit/test_jobs_routes_import.py \ + tests/unit/test_dailypaper.py \ + tests/unit/test_paper_judge.py \ + tests/unit/test_memory_module.py \ + tests/unit/test_memory_metric_collector.py \ + tests/unit/test_llm_service.py \ + tests/unit/test_di_container.py \ + tests/unit/test_pipeline.py \ + tests/unit/repro/test_agents.py \ + tests/unit/repro/test_blueprint.py \ + tests/unit/repro/test_memory.py \ + tests/unit/repro/test_orchestrator.py \ + tests/unit/repro/test_rag.py \ + tests/integration/test_eventlog_sqlalchemy.py \ + tests/integration/test_crawler_contract_parsers.py \ + tests/integration/test_arxiv_connector_fixture.py \ + tests/integration/test_reddit_connector_fixture.py \ + tests/integration/test_x_importer_fixture.py \ + tests/integration/test_repro_deepcode.py \ + tests/test_generation_agent.py \ + tests/test_repro_planning.py \ + tests/test_repro_agent.py \ + tests/test_repro_e2e.py \ + tests/test_repro_models.py \ + tests/e2e/test_api_track_fullstack_offline.py diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..eb9d5404 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectName=PaperBot +sonar.sourceEncoding=UTF-8 + +sonar.sources=src,web/src +sonar.tests=tests,web/src +sonar.test.inclusions=tests/**/*,web/src/**/*.test.ts,web/src/**/*.test.tsx,web/src/**/*.spec.ts,web/src/**/*.spec.tsx +sonar.exclusions=web/src/**/*.test.ts,web/src/**/*.test.tsx,web/src/**/*.spec.ts,web/src/**/*.spec.tsx,**/__pycache__/**,**/*.pyc +sonar.coverage.exclusions=tests/**,web/src/**/*.test.ts,web/src/**/*.test.tsx,web/src/**/*.spec.ts,web/src/**/*.spec.tsx + +sonar.python.coverage.reportPaths=coverage.xml +sonar.javascript.lcov.reportPaths=web/coverage/lcov.info +sonar.typescript.lcov.reportPaths=web/coverage/lcov.info diff --git a/web/package-lock.json b/web/package-lock.json index 8743f3ed..bc756c81 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -58,6 +58,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^2.1.4", "babel-plugin-react-compiler": "1.0.0", "eslint": "^9", "eslint-config-next": "16.1.0", @@ -198,6 +199,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@auth/core": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.0.tgz", @@ -467,6 +482,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -1603,6 +1625,34 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1960,6 +2010,17 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -6011,6 +6072,66 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", @@ -6823,6 +6944,39 @@ "node": ">= 20" } }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", @@ -7106,6 +7260,19 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8270,6 +8437,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9303,6 +9477,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9500,6 +9691,28 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -9513,6 +9726,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -9721,6 +9960,13 @@ "node": ">=16.9.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -10077,6 +10323,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -10349,6 +10605,60 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -10367,6 +10677,22 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -10868,6 +11194,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", @@ -11838,6 +12205,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/monaco-editor": { "version": "0.55.1", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", @@ -12264,6 +12641,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12337,6 +12721,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -13625,6 +14033,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -13694,6 +14115,70 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -13821,6 +14306,46 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -13955,6 +14480,60 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/throttleit": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", @@ -14918,6 +15497,101 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/web/package.json b/web/package.json index eb37ad43..76bae707 100644 --- a/web/package.json +++ b/web/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "eslint", "test": "vitest run", + "test:coverage": "vitest run --coverage", "test:e2e": "node ./scripts/run-playwright-e2e.mjs", "test:e2e:ui": "node ./scripts/run-playwright-e2e.mjs --ui", "test:e2e:headed": "node ./scripts/run-playwright-e2e.mjs --headed", @@ -64,6 +65,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^2.1.4", "babel-plugin-react-compiler": "1.0.0", "eslint": "^9", "eslint-config-next": "16.1.0", diff --git a/web/vitest.config.ts b/web/vitest.config.ts index 02949a02..1c26bb41 100644 --- a/web/vitest.config.ts +++ b/web/vitest.config.ts @@ -1,6 +1,6 @@ import path from "node:path" -import { defineConfig } from "vitest/config" +import { configDefaults, defineConfig } from "vitest/config" export default defineConfig({ resolve: { @@ -10,5 +10,22 @@ export default defineConfig({ }, test: { environment: "node", + environmentMatchGlobs: [ + ["src/components/**/*.test.tsx", "jsdom"], + ["src/components/**/*.test.ts", "jsdom"], + ], + exclude: [...configDefaults.exclude, "e2e/**"], + coverage: { + provider: "v8", + reporter: ["text", "lcov"], + reportsDirectory: "./coverage", + include: ["src/**/*.ts", "src/**/*.tsx"], + exclude: [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + ], + }, }, }) From fa7dffc366d1993442334898c25a9cf3f66a4af1 Mon Sep 17 00:00:00 2001 From: jerry609 <1772030600@qq.com> Date: Sun, 15 Mar 2026 14:25:06 +0800 Subject: [PATCH 2/4] ci: pin sonar scan action sha --- .github/workflows/sonarcloud.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 38081073..7f9a8a5b 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -92,7 +92,7 @@ jobs: ${{ runner.os }}-sonar-cache - name: SonarCloud scan - uses: SonarSource/sonarqube-scan-action@v7 + uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: From 1cfe6356c9c7501d98cbded259baf6d422a83cf8 Mon Sep 17 00:00:00 2001 From: jerry609 <1772030600@qq.com> Date: Sun, 15 Mar 2026 14:31:05 +0800 Subject: [PATCH 3/4] ci: skip sonar scan without credentials --- .github/workflows/sonarcloud.yml | 39 ++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 7f9a8a5b..d08d2670 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -28,6 +28,9 @@ jobs: env: PYTHONPATH: src PAPERBOT_DB_URL: sqlite:///data/paperbot-ci.db + SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION || 'jerry609' }} + SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY || 'jerry609_PaperBot' }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} steps: - name: Checkout repository @@ -35,24 +38,31 @@ jobs: with: fetch-depth: 0 - - name: Validate SonarCloud settings + - name: Check SonarCloud settings + id: sonar_config shell: bash - env: - SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} - SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | set -euo pipefail - test -n "${SONAR_ORGANIZATION}" || { echo "Missing repository variable SONAR_ORGANIZATION"; exit 1; } - test -n "${SONAR_PROJECT_KEY}" || { echo "Missing repository variable SONAR_PROJECT_KEY"; exit 1; } - test -n "${SONAR_TOKEN}" || { echo "Missing repository secret SONAR_TOKEN"; exit 1; } + if [ -z "${SONAR_TOKEN}" ]; then + echo "scan_enabled=false" >> "${GITHUB_OUTPUT}" + { + echo "### SonarCloud scan skipped" + echo + echo "Repository secret SONAR_TOKEN is not configured." + echo "The workflow remains available and will start scanning once the secret is added." + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + echo "scan_enabled=true" >> "${GITHUB_OUTPUT}" - name: Set up Python + if: steps.sonar_config.outputs.scan_enabled == 'true' uses: actions/setup-python@v5 with: python-version: "3.11" - name: Cache pip + if: steps.sonar_config.outputs.scan_enabled == 'true' uses: actions/cache@v4 with: path: ~/.cache/pip @@ -61,14 +71,17 @@ jobs: ${{ runner.os }}-sonar-pip- - name: Install backend dependencies + if: steps.sonar_config.outputs.scan_enabled == 'true' run: | python -m pip install -U pip python -m pip install -r requirements-ci.txt - name: Run backend tests with coverage + if: steps.sonar_config.outputs.scan_enabled == 'true' run: bash scripts/ci/run_backend_offline_gates.sh --cov=src --cov-report=xml:coverage.xml - name: Set up Node.js + if: steps.sonar_config.outputs.scan_enabled == 'true' uses: actions/setup-node@v4 with: node-version: 20 @@ -76,14 +89,17 @@ jobs: cache-dependency-path: web/package-lock.json - name: Install web dependencies + if: steps.sonar_config.outputs.scan_enabled == 'true' working-directory: web run: npm ci - name: Run web tests with coverage + if: steps.sonar_config.outputs.scan_enabled == 'true' working-directory: web run: npm run test:coverage - name: Cache SonarCloud packages + if: steps.sonar_config.outputs.scan_enabled == 'true' uses: actions/cache@v4 with: path: ~/.sonar/cache @@ -92,13 +108,12 @@ jobs: ${{ runner.os }}-sonar-cache - name: SonarCloud scan + if: steps.sonar_config.outputs.scan_enabled == 'true' uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: args: > - -Dsonar.organization=${{ vars.SONAR_ORGANIZATION }} - -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} + -Dsonar.organization=${{ env.SONAR_ORGANIZATION }} + -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300 From ab4dfd817f0c62232efe4071f663cbe74514eeb5 Mon Sep 17 00:00:00 2001 From: jerry609 <1772030600@qq.com> Date: Sun, 15 Mar 2026 14:35:46 +0800 Subject: [PATCH 4/4] ci: scope sonar workflow permissions --- .github/workflows/sonarcloud.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index d08d2670..8953ecc1 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -15,16 +15,15 @@ concurrency: group: sonarcloud-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -permissions: - contents: read - pull-requests: read - jobs: scan: name: SonarCloud Scan if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: read + pull-requests: read env: PYTHONPATH: src PAPERBOT_DB_URL: sqlite:///data/paperbot-ci.db