ci: configure Codecov coverage uploads (#150)

This commit is contained in:
Andrey Avtomonov 2026-05-19 16:56:48 +02:00 committed by GitHub
parent 75bb4f9497
commit 366c44f224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 222 additions and 9 deletions

View file

@ -191,9 +191,8 @@ jobs:
coverage:
name: Coverage
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
CODECOV_TOKEN_CONFIGURED: ${{ secrets.CODECOV_TOKEN != '' }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@ -232,25 +231,39 @@ jobs:
run: pnpm run test:coverage:ts
- name: Upload TypeScript coverage
if: env.CODECOV_TOKEN_CONFIGURED == 'true'
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4
with:
use_oidc: true
token: ${{ secrets.CODECOV_TOKEN }}
slug: Kaelio/ktx
files: ./packages/cli/coverage/lcov.info,./packages/connector-bigquery/coverage/lcov.info,./packages/connector-clickhouse/coverage/lcov.info,./packages/connector-mysql/coverage/lcov.info,./packages/connector-postgres/coverage/lcov.info,./packages/connector-snowflake/coverage/lcov.info,./packages/connector-sqlite/coverage/lcov.info,./packages/connector-sqlserver/coverage/lcov.info,./packages/context/coverage/lcov.info,./packages/llm/coverage/lcov.info
flags: typescript
name: typescript
fail_ci_if_error: false
disable_search: true
fail_ci_if_error: true
- name: Warn when Codecov token is missing for TypeScript
if: env.CODECOV_TOKEN_CONFIGURED != 'true'
run: echo "::warning::CODECOV_TOKEN is not configured; skipping TypeScript coverage upload"
- name: Generate Python coverage
run: pnpm run test:coverage:py
- name: Upload Python coverage
if: env.CODECOV_TOKEN_CONFIGURED == 'true'
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4
with:
use_oidc: true
token: ${{ secrets.CODECOV_TOKEN }}
slug: Kaelio/ktx
files: ./coverage/python.xml
flags: python
name: python
fail_ci_if_error: false
disable_search: true
fail_ci_if_error: true
- name: Warn when Codecov token is missing for Python
if: env.CODECOV_TOKEN_CONFIGURED != 'true'
run: echo "::warning::CODECOV_TOKEN is not configured; skipping Python coverage upload"
artifact-checks:
name: Artifact checks

View file

@ -10,7 +10,8 @@
<p align="center">
<a href="https://www.npmjs.com/package/@kaelio/ktx"><img src="https://img.shields.io/npm/v/@kaelio/ktx?style=flat-square&color=f97316" alt="npm version" /></a>
<a href="https://codecov.io/gh/Kaelio/ktx"><img src="https://codecov.io/gh/Kaelio/ktx/branch/main/graph/badge.svg" alt="Codecov" /></a>
<a href="https://codecov.io/gh/Kaelio/ktx"><img src="https://codecov.io/gh/Kaelio/ktx/graph/badge.svg?branch=main" alt="Codecov" /></a>
<a href="https://docs.kaelio.com/ktx/docs/"><img src="https://img.shields.io/badge/docs-KTX-f97316?style=flat-square" alt="Documentation" /></a>
<a href="https://github.com/Kaelio/ktx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square" alt="License" /></a>
<a href="https://github.com/Kaelio/ktx"><img src="https://img.shields.io/github/stars/Kaelio/ktx?style=flat-square" alt="GitHub stars" /></a>
</p>

109
codecov.yml Normal file
View file

@ -0,0 +1,109 @@
codecov:
branch: main
require_ci_to_pass: true
notify:
after_n_builds: 2
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default:
target: auto
threshold: 1%
if_ci_failed: error
typescript:
target: auto
threshold: 1%
flags:
- typescript
if_ci_failed: error
python:
target: auto
threshold: 1%
flags:
- python
if_ci_failed: error
patch:
default:
target: 75%
threshold: 5%
if_ci_failed: error
informational: true
comment:
layout: "header, diff, flags, components, files"
behavior: default
require_changes: false
require_base: false
require_head: true
flags:
typescript:
paths:
- packages/
carryforward: false
python:
paths:
- python/
carryforward: false
component_management:
individual_components:
- component_id: pkg_cli
name: CLI
paths:
- packages/cli/src/**
- component_id: pkg_context
name: Context engine
paths:
- packages/context/src/**
- component_id: pkg_llm
name: LLM
paths:
- packages/llm/src/**
- component_id: connector_bigquery
name: BigQuery connector
paths:
- packages/connector-bigquery/src/**
- component_id: connector_clickhouse
name: ClickHouse connector
paths:
- packages/connector-clickhouse/src/**
- component_id: connector_mysql
name: MySQL connector
paths:
- packages/connector-mysql/src/**
- component_id: connector_postgres
name: Postgres connector
paths:
- packages/connector-postgres/src/**
- component_id: connector_snowflake
name: Snowflake connector
paths:
- packages/connector-snowflake/src/**
- component_id: connector_sqlite
name: SQLite connector
paths:
- packages/connector-sqlite/src/**
- component_id: connector_sqlserver
name: SQL Server connector
paths:
- packages/connector-sqlserver/src/**
- component_id: py_semantic_layer
name: Python semantic layer
paths:
- python/ktx-sl/semantic_layer/**
- component_id: py_daemon
name: Python daemon
paths:
- python/ktx-daemon/src/ktx_daemon/**
ignore:
- docs-site/**
- examples/**
- packages/*/coverage/**
- packages/*/dist/**
- python/ktx-sl/demos/**

View file

@ -42,7 +42,7 @@
"test": "node --test scripts/*.test.mjs && pnpm --filter './packages/*' run test",
"test:coverage": "pnpm run test:coverage:ts && pnpm run test:coverage:py",
"test:coverage:py": "uv run pytest --cov=python/ktx-sl/semantic_layer --cov=python/ktx-daemon/src/ktx_daemon --cov-report=xml:coverage/python.xml --cov-report=term",
"test:coverage:ts": "pnpm --filter './packages/*' run build && pnpm --filter './packages/*' run test --coverage --coverage.reporter=lcov --coverage.exclude='dist/**'",
"test:coverage:ts": "pnpm --filter './packages/*' run build && pnpm --filter './packages/*' run test --coverage --coverage.reporter=lcov --coverage.exclude='dist/**' && node scripts/normalize-lcov-paths.mjs packages/*/coverage/lcov.info",
"test:slow": "pnpm --filter @ktx/context run test:slow && pnpm --filter @ktx/cli run test:slow",
"type-check": "pnpm --filter './packages/*' run type-check"
},

View file

@ -0,0 +1,59 @@
#!/usr/bin/env node
import { readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
function toPosix(filePath) {
return filePath.replace(/\\/g, '/');
}
export function normalizeLcovContent(content, packagePath) {
const normalizedPackagePath = toPosix(packagePath).replace(/\/$/, '');
return content.replace(/^SF:(.+)$/gm, (line, sourcePath) => {
const normalizedSourcePath = toPosix(sourcePath);
if (
path.isAbsolute(sourcePath) ||
normalizedSourcePath.startsWith(`${normalizedPackagePath}/`) ||
normalizedSourcePath.startsWith('../')
) {
return line;
}
return `SF:${normalizedPackagePath}/${normalizedSourcePath}`;
});
}
export async function normalizeLcovFile(rootDir, reportPath) {
const absoluteReportPath = path.resolve(rootDir, reportPath);
const packagePath = toPosix(path.relative(rootDir, path.dirname(path.dirname(absoluteReportPath))));
const content = await readFile(absoluteReportPath, 'utf8');
const normalizedContent = normalizeLcovContent(content, packagePath);
if (normalizedContent !== content) {
await writeFile(absoluteReportPath, normalizedContent);
}
}
async function main() {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(scriptDir, '..');
const reportPaths = process.argv.slice(2);
if (reportPaths.length === 0) {
throw new Error('Usage: normalize-lcov-paths.mjs <coverage/lcov.info> [...]');
}
for (const reportPath of reportPaths) {
await normalizeLcovFile(rootDir, reportPath);
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch((error) => {
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});
}

View file

@ -0,0 +1,31 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { normalizeLcovContent } from './normalize-lcov-paths.mjs';
describe('normalizeLcovContent', () => {
it('prefixes relative LCOV source paths with the package path', () => {
const input = ['TN:', 'SF:src/index.ts', 'SF:src\\windows.ts', 'DA:1,1', 'end_of_record'].join('\n');
assert.equal(
normalizeLcovContent(input, 'packages/context'),
[
'TN:',
'SF:packages/context/src/index.ts',
'SF:packages/context/src/windows.ts',
'DA:1,1',
'end_of_record',
].join('\n'),
);
});
it('leaves already-normalized and absolute paths unchanged', () => {
const input = [
'SF:packages/cli/src/index.ts',
'SF:/tmp/repo/packages/cli/src/index.ts',
'SF:../shared/source.ts',
].join('\n');
assert.equal(normalizeLcovContent(input, 'packages/cli'), input);
});
});