fix(cli): allow debug telemetry under opt-out env

This commit is contained in:
Andrey Avtomonov 2026-05-26 07:59:47 +02:00
parent 4c303db2a4
commit 8b3274d88f
2 changed files with 72 additions and 3 deletions

View file

@ -55,6 +55,10 @@ const emittedProjectSnapshots = new Set<string>();
const MCP_SAMPLE_RATE = 0.1 as const;
let mcpSampled: boolean | undefined;
function telemetryDebugEnabled(): boolean {
return process.env.KTX_TELEMETRY_DEBUG === '1';
}
export function shouldEmitMcpTelemetry(): boolean {
mcpSampled ??= Math.random() < MCP_SAMPLE_RATE;
return mcpSampled;
@ -71,19 +75,21 @@ export async function emitTelemetryEvent<Name extends TelemetryEventName>(input:
packageInfo?: KtxCliPackageInfo;
projectDir?: string;
}): Promise<void> {
const debug = telemetryDebugEnabled();
const identity = await loadTelemetryIdentity({
stdoutIsTTY: input.io.stdout.isTTY === true,
stderr: input.io.stderr,
env: process.env,
});
if (!identity.enabled || !identity.installId) {
if ((!identity.enabled || !identity.installId) && !debug) {
return;
}
const packageInfo = input.packageInfo ?? getKtxCliPackageInfo();
const installId = identity.installId ?? 'debug';
const projectId = input.projectDir ? computeTelemetryProjectId(identity.installId, input.projectDir) : undefined;
const projectId = input.projectDir ? computeTelemetryProjectId(installId, input.projectDir) : undefined;
await trackTelemetryEvent({
event: buildTelemetryEvent(
input.name,
@ -93,7 +99,7 @@ export async function emitTelemetryEvent<Name extends TelemetryEventName>(input:
}),
input.fields,
),
distinctId: identity.installId,
distinctId: installId,
projectId,
env: process.env,
stderr: input.io.stderr,

View file

@ -0,0 +1,63 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { KtxCliIo } from '../../src/cli-runtime.js';
import { emitTelemetryEvent } from '../../src/telemetry/index.js';
function makeIo(): { io: KtxCliIo; stderr: () => string } {
let stderr = '';
return {
io: {
stdout: {
isTTY: true,
write: () => {},
},
stderr: {
write: (chunk) => {
stderr += chunk;
},
},
},
stderr: () => stderr,
};
}
describe('emitTelemetryEvent', () => {
let homeDir: string;
beforeEach(async () => {
homeDir = await mkdtemp(join(tmpdir(), 'ktx-telemetry-index-'));
vi.stubEnv('HOME', homeDir);
});
afterEach(async () => {
vi.unstubAllEnvs();
await rm(homeDir, { recursive: true, force: true });
});
it('prints debug telemetry when live telemetry is disabled without creating an identity file', async () => {
vi.stubEnv('KTX_TELEMETRY_DEBUG', '1');
vi.stubEnv('KTX_TELEMETRY_DISABLED', '1');
vi.stubEnv('DO_NOT_TRACK', '1');
const testIo = makeIo();
const projectDir = join(homeDir, 'private-project');
await emitTelemetryEvent({
name: 'connection_added',
projectDir,
io: testIo.io,
packageInfo: { name: '@kaelio/ktx', version: '0.0.0-test' },
fields: {
driver: 'sqlite',
isDemoConnection: false,
},
});
expect(testIo.stderr()).toContain('[telemetry]');
expect(testIo.stderr()).toContain('"event":"connection_added"');
expect(testIo.stderr()).not.toContain(projectDir);
await expect(readFile(join(homeDir, '.ktx', 'telemetry.json'), 'utf-8')).rejects.toThrow();
});
});