mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* build(test): make spec tsconfigs resolve what Jest resolves Lib spec tsconfigs used module: commonjs with node10 resolution, which cannot see Angular's exports-only secondary entry points, and dropped global.d.ts, so tsc reported thousands of resolution errors and no window.electron typing. Switch them to module: preserve with bundler resolution (ts-jest still forces CommonJS emit outside ESM mode), add global.d.ts to every spec program, type jest.unstable_mockModule for the ESM workspace, include the ui-epg and ui-playback specs that jest.web-esm.workspace.ts runs under the web spec config, and drop the snack-bar stub that shadowed the real Material types. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci(test): gate spec type-checking with typecheck:spec Add tools/typecheck/spec-typecheck.mjs, which runs tsc --noEmit over every tsconfig.spec.json with a small pool and fails on any diagnostic, wire it into the unit-and-typecheck job after typecheck:ci, and document the gate and the spec tsconfig conventions in the validation map. Also bring the non-Tier-A spec configs (remote-control-web, ui-remote-control, stalker-mock-server) to the same conventions so the gate covers the whole workspace. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: fix the spec type errors surfaced by typecheck:spec With the spec programs resolving modules and ambient typings correctly, tsc reported 432 genuine errors across the Tier A projects: read-only capability flags assigned on Partial<> doubles, signal-store values used as types, fixtures missing required fields, index-signature property access, partial bridge doubles cast through incompatible shapes, and deferred resolvers narrowed to never. Type the doubles instead of casting to any: writable mapped types for capability flags, InstanceType<typeof StalkerStore>, typed jest.fn signatures, protectedState: false on test signal stores, and completed fixtures. Production changes are limited to bracket access for index-signature properties under the libs' noPropertyAccessFromIndexSignature setting and two narrowing guards in the global favorites loader. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(playback): use the ESM setup's jest global in the controls fixtures The fixture imported jest from @jest/globals, which is not a direct dependency. Jest provides that module at runtime, so tests passed, but on a clean pnpm install tsc cannot resolve it and typecheck:spec failed in CI. The ESM test setup already installs import.meta.jest as the global, typed by @types/jest, as the other ESM specs use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: type the parental lock doubles merged since the gate was written The parental lock feature (#1601) and the Stalker actor route landed on master with spec doubles declared as zero-argument jest.fn()s that the tests then drive with the real arguments, plus a copy of the ResizableDirective override imported from a library that does not export it. Give the doubles the lock service's real signatures, drop the dead override as in the sibling layout specs, use bracket access for the actor route's personId param, and keep the Stalker layout spec within the 1200-line limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
491 lines
21 KiB
YAML
491 lines
21 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
workflow_dispatch:
|
|
|
|
# Superseded PR pushes cancel their still-running checks. Non-PR runs get a
|
|
# unique group (run_id) because GitHub keeps at most one pending run per
|
|
# group even with cancel-in-progress: false — a shared ref group would let a
|
|
# rapid master push silently replace a queued sibling and leave a merged
|
|
# commit without a lint/test record.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
actionlint:
|
|
name: Workflow lint
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
|
|
# Image pinned by digest (tag 1.7.12). False positives are
|
|
# suppressed in .github/actionlint.yaml; shellcheck runs at
|
|
# warning+ severity so style/info notes in long release scripts
|
|
# don't fail CI while real quoting/logic bugs still do.
|
|
- name: Run actionlint
|
|
uses: docker://rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
|
|
with:
|
|
args: -color
|
|
env:
|
|
SHELLCHECK_OPTS: --severity=warning
|
|
|
|
release-note-gate:
|
|
name: Release note gate
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
|
|
# The gate scripts are dependency-free Node, so this job skips
|
|
# pnpm install entirely and stays cheap.
|
|
- name: Validate release note format
|
|
run: node tools/release/build-release-notes.mjs --validate
|
|
|
|
# Labels are fetched live rather than read from the (stale) event
|
|
# payload, so applying `no-release-note` and re-running the check
|
|
# works without a new push. Policy lives in a unit-tested script,
|
|
# not in workflow bash.
|
|
- name: Require a release note for user-visible changes
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?per_page=100" \
|
|
--paginate --jq '[.[] | {filename, status}]' |
|
|
jq -s 'add // []' > /tmp/pr-files.json
|
|
|
|
gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels?per_page=100" \
|
|
--paginate --jq '[.[].name]' |
|
|
jq -s 'add // []' > /tmp/pr-labels.json
|
|
|
|
jq -n \
|
|
--slurpfile files /tmp/pr-files.json \
|
|
--slurpfile labels /tmp/pr-labels.json \
|
|
'{files: $files[0], labels: $labels[0]}' |
|
|
node tools/release/check-release-note-gate.mjs
|
|
|
|
lint:
|
|
name: Lint
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
# nx affected needs the merge-base with the PR target branch.
|
|
fetch-depth: 0
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v6.0.10
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# PRs lint only affected projects for faster feedback; root config
|
|
# or lockfile changes make every project affected, so the
|
|
# module-boundary and max-lines rules cannot be dodged this way.
|
|
- name: Lint affected projects (PR)
|
|
if: github.event_name == 'pull_request'
|
|
run: pnpm nx affected --target=lint --base=origin/${{ github.base_ref }} --head=HEAD --parallel=3 --output-style=static
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
|
|
- name: Lint all projects (master)
|
|
if: github.event_name != 'pull_request'
|
|
run: pnpm nx run-many --target=lint --all --parallel=3 --output-style=static
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
|
|
initial-bytes-ratchet:
|
|
name: Initial bytes ratchet
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v6.0.10
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# The ratchet below compares a measurement with the baselines file
|
|
# of the same commit, so it cannot see a change that grows the
|
|
# payload and raises the baseline to match. Compare the file with
|
|
# the revision this one is measured against instead: the target
|
|
# branch for a pull request, the previous head for a master push,
|
|
# master for a manual dispatch. Any raised limit, widened tolerance
|
|
# or removed entry fails.
|
|
- name: Refuse baseline increases against the previous revision
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
BASE_REF: ${{ github.base_ref }}
|
|
BEFORE_SHA: ${{ github.event.before }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$EVENT_NAME" in
|
|
pull_request)
|
|
git fetch --no-tags --depth=1 origin "$BASE_REF"
|
|
;;
|
|
push)
|
|
if [ -z "$BEFORE_SHA" ] || [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then
|
|
echo "No previous revision for this push; nothing to compare against."
|
|
exit 0
|
|
fi
|
|
git fetch --no-tags --depth=1 origin "$BEFORE_SHA"
|
|
;;
|
|
*)
|
|
git fetch --no-tags --depth=1 origin master
|
|
;;
|
|
esac
|
|
git show "FETCH_HEAD:tools/performance/journey-baselines.json" > /tmp/base-journey-baselines.json 2>/dev/null ||
|
|
rm -f /tmp/base-journey-baselines.json
|
|
node tools/performance/check-baseline-direction.mjs \
|
|
--base /tmp/base-journey-baselines.json \
|
|
--head tools/performance/journey-baselines.json
|
|
|
|
# The production configuration is what users download; measuring
|
|
# any other build would ratchet a number nobody ships.
|
|
- name: Build web app (production)
|
|
run: pnpm nx build web --skip-nx-cache
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
|
|
# Fails when renderer.initialBytes exceeds the value committed in
|
|
# tools/performance/journey-baselines.json. Baselines only move
|
|
# down, with the printed measurement as evidence; the contract is
|
|
# docs/architecture/performance-journeys.md.
|
|
- name: Check renderer.initialBytes against the baseline
|
|
run: pnpm run perf:initial-bytes:check
|
|
|
|
- name: Upload journey summary
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: performance-journey-summary
|
|
path: dist/performance/
|
|
retention-days: 14
|
|
|
|
# Decides whether a pull request can move the performance journeys, so
|
|
# the journeys job below does not spend a runner on docs-only changes.
|
|
# Pushes to master and manual dispatches always run them.
|
|
performance-journeys-scope:
|
|
name: Performance journeys scope
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
outputs:
|
|
run: ${{ steps.scope.outputs.run }}
|
|
|
|
steps:
|
|
# The PR checkout is the merge commit: its first parent is the
|
|
# target branch, so two commits are enough to diff the PR.
|
|
- name: Checkout code
|
|
if: github.event_name == 'pull_request'
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 2
|
|
|
|
# Anything can move a journey (application code, the harness, root
|
|
# build inputs such as .nvmrc, nx.json or tsconfig.base.json), so
|
|
# the filter lists what cannot: the same paths the E2E workflow
|
|
# ignores, plus release notes.
|
|
- name: Detect journey-relevant changes
|
|
id: scope
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$EVENT_NAME" != "pull_request" ]; then
|
|
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
relevant="$(git diff --name-only HEAD^1 HEAD |
|
|
grep -vE '\.md$|^docs/|^\.plans/|^\.codex/|^\.claude/|^\.changes/|^apps/website/' || true)"
|
|
if [ -n "$relevant" ]; then
|
|
echo "Journey-relevant changes:"
|
|
echo "$relevant"
|
|
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "No journey-relevant changes; skipping the performance journeys."
|
|
echo "run=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# Runs the journey benchmarks (docs/architecture/performance-journeys.md)
|
|
# on the canonical Linux runner and uploads the summary as evidence.
|
|
performance-journeys:
|
|
name: Performance journeys
|
|
needs: performance-journeys-scope
|
|
if: needs.performance-journeys-scope.outputs.run == 'true'
|
|
runs-on: ubuntu-latest
|
|
# The electron-performance build is the bulk of the time; the launch
|
|
# journey itself is six fresh Electron processes plus one seeding run.
|
|
timeout-minutes: 30
|
|
# Warn-only for the first two weeks of plan item B3: a failure is
|
|
# visible on the run but does not fail the workflow.
|
|
continue-on-error: true
|
|
env:
|
|
NX_SKIP_NX_CACHE: true
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v6.0.10
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# The journeys drive Electron through Playwright's _electron API
|
|
# and never launch a Playwright browser, so no `playwright
|
|
# install`. The runner image ships Electron's shared libraries and
|
|
# xvfb; fail fast with a clear message if an image update drops one.
|
|
- name: Check Electron runtime dependencies
|
|
run: |
|
|
command -v xvfb-run || { echo "::error::xvfb-run is missing on the runner"; exit 1; }
|
|
missing="$(ldd node_modules/electron/dist/electron | grep 'not found' || true)"
|
|
if [ -n "$missing" ]; then
|
|
echo "::error::Electron is missing shared libraries:"
|
|
echo "$missing"
|
|
exit 1
|
|
fi
|
|
|
|
# The Nx target builds electron-backend:build-performance first
|
|
# and playwright.journeys.config.ts starts the Xtream mock server.
|
|
- name: Run the performance journeys
|
|
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" pnpm run perf:journeys
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
|
|
# Every run writes a fresh timestamped directory, so a clean
|
|
# checkout must hold exactly one summary.
|
|
- name: Locate the journey summary
|
|
id: summary
|
|
run: |
|
|
set -euo pipefail
|
|
mapfile -t summaries < <(find dist/performance/journeys -mindepth 2 -maxdepth 2 -name summary.json)
|
|
if [ "${#summaries[@]}" -ne 1 ]; then
|
|
echo "::error::Expected one journey summary, found ${#summaries[@]}"
|
|
exit 1
|
|
fi
|
|
echo "path=${summaries[0]}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Report journey measurements
|
|
env:
|
|
SUMMARY: ${{ steps.summary.outputs.path }}
|
|
run: |
|
|
set -euo pipefail
|
|
jq -r '
|
|
"## Performance journeys (\(.harness.platform), \(.harness.measuredIterations) measured iterations)", "",
|
|
(.journeys | to_entries[] | .key as $journey | .value as $j |
|
|
"### `\($journey)`", "",
|
|
"| Measurement | Value | Iterations |",
|
|
"| --- | ---: | --- |",
|
|
(($j.counters // {}) | to_entries[] |
|
|
($j.counterStability[.key] // {}) as $s |
|
|
"| `\(.key)` | \(.value) | \(($s.values // []) | map(tostring) | join(", "))\(if $s.stable == false then " (unstable)" else "" end) |"),
|
|
(($j.wallClock // {}) | to_entries[] | "| `\(.key)` | \(.value) | |"),
|
|
"")
|
|
' "$SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Upload journey summaries
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: performance-journeys
|
|
path: dist/performance/journeys/
|
|
if-no-files-found: warn
|
|
retention-days: 14
|
|
|
|
unit-and-typecheck:
|
|
name: Unit Tests and Typechecks
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: read
|
|
# The scope step lists the PR's changed files through the API.
|
|
pull-requests: read
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v6.0.10
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Validate agent guidance
|
|
run: pnpm run agents:validate
|
|
|
|
- name: Validate Nx dependency version policy
|
|
run: pnpm run deps:nx:validate
|
|
|
|
- name: Validate Vite dev-server transform filter patch
|
|
run: pnpm run deps:vite:test
|
|
|
|
- name: Validate electron-builder keychain password patch
|
|
run: pnpm run deps:electron-builder:test
|
|
|
|
- name: Validate stylesheet Nx inputs
|
|
run: pnpm run styles:inputs:validate
|
|
|
|
- name: Typecheck web and Electron entry points
|
|
run: pnpm run typecheck:ci
|
|
|
|
- name: Typecheck Jest spec programs
|
|
run: pnpm run typecheck:spec:test && pnpm run typecheck:spec
|
|
|
|
- name: Check i18n drift
|
|
run: pnpm run i18n:check
|
|
|
|
# A pull request whose changes cannot reach any Tier A test (docs,
|
|
# notes, other workflows, website, E2E and mock-server apps, release
|
|
# and packaging tooling, a scripts-only package.json edit) skips the
|
|
# suite. The allowlist lives in a unit-tested script; anything it
|
|
# does not know runs everything, and master always runs everything.
|
|
- name: Decide unit coverage scope
|
|
id: scope
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
CHANGED_FILES: ${{ github.event.pull_request.changed_files }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$EVENT_NAME" != "pull_request" ]; then
|
|
echo "Not a pull request; running the full suite."
|
|
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# The list-files endpoint stops at 3,000 files; a truncated
|
|
# list could hide a file that needs the suite.
|
|
if [ "${CHANGED_FILES:-0}" -ge 3000 ]; then
|
|
echo "PR changes ${CHANGED_FILES} files, beyond the API listing limit; running the full suite."
|
|
echo "run=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
git fetch --no-tags --depth=1 origin "$BASE_SHA"
|
|
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?per_page=100" \
|
|
--paginate --jq '.[] | .filename, (.previous_filename // empty)' |
|
|
node tools/coverage/unit-coverage-scope.mjs --base FETCH_HEAD --github-output
|
|
|
|
# Jest's transform cache (TypeScript/Angular transpilation plus
|
|
# coverage instrumentation, keyed by file content) is persisted
|
|
# between runs. Only master pushes and maintainer dispatches save
|
|
# it; pull requests restore it and never write, so a PR cannot plant
|
|
# an entry that a later master run would read.
|
|
- name: Restore Jest transform cache
|
|
if: steps.scope.outputs.run == 'true' && github.event_name != 'push'
|
|
uses: actions/cache/restore@v6
|
|
with:
|
|
path: ${{ runner.temp }}/jest-cache
|
|
key: jest-transform-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig.base.json', 'jest.preset.js', 'jest.web-esm.workspace.ts', 'apps/web/jest.config.ts') }}-${{ github.run_id }}
|
|
restore-keys: |
|
|
jest-transform-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig.base.json', 'jest.preset.js', 'jest.web-esm.workspace.ts', 'apps/web/jest.config.ts') }}-
|
|
|
|
- name: Run Tier A unit coverage suite
|
|
if: steps.scope.outputs.run == 'true'
|
|
run: pnpm run coverage:ci
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
JEST_CACHE_DIRECTORY: ${{ runner.temp }}/jest-cache
|
|
|
|
- name: Validate coverage tooling (suite skipped)
|
|
if: steps.scope.outputs.run != 'true'
|
|
run: pnpm run coverage:tools:test && pnpm run coverage:policy:check
|
|
|
|
# Master pushes start from an empty cache, so the saved cache holds
|
|
# exactly the current tree and does not grow run over run.
|
|
- name: Save Jest transform cache
|
|
if: >-
|
|
steps.scope.outputs.run == 'true' &&
|
|
(github.event_name == 'workflow_dispatch' ||
|
|
(github.event_name == 'push' && github.ref == 'refs/heads/master'))
|
|
uses: actions/cache/save@v6
|
|
with:
|
|
path: ${{ runner.temp }}/jest-cache
|
|
key: jest-transform-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig.base.json', 'jest.preset.js', 'jest.web-esm.workspace.ts', 'apps/web/jest.config.ts') }}-${{ github.run_id }}
|
|
|
|
- name: Run Tier B/C validation commands
|
|
run: node tools/coverage/check-coverage-policy.mjs --run-non-tier-a
|
|
env:
|
|
CI: true
|
|
NX_TASKS_RUNNER_DYNAMIC_OUTPUT: false
|
|
|
|
- name: Upload unit coverage artifact
|
|
if: always() && steps.scope.outputs.run == 'true'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: unit-coverage
|
|
path: |
|
|
coverage/merged/
|
|
retention-days: 14
|
|
|
|
- name: Upload unit coverage to Codecov
|
|
if: always() && steps.scope.outputs.run == 'true'
|
|
uses: codecov/codecov-action@v7
|
|
with:
|
|
files: ./coverage/merged/lcov.info,./coverage/merged/cobertura-coverage.xml
|
|
flags: unit
|
|
name: iptvnator-unit
|
|
fail_ci_if_error: false
|
|
handle_no_reports_found: true
|
|
disable_search: true
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|