UNPKG

wgc

Version:

The official CLI tool to manage the GraphQL Federation Platform Cosmo

192 lines 6.55 kB
import crypto from 'node:crypto'; import os from 'node:os'; import { PostHog } from 'posthog-node'; import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; import { isAuthenticated } from '../commands/auth/utils.js'; import { config, getBaseHeaders } from './config.js'; import { CreateClient } from './client/client.js'; // Environment variables to allow opting out of telemetry // Support for COSMO_TELEMETRY_DISABLED and Console Do Not Track standard const TELEMETRY_DISABLED = process.env.COSMO_TELEMETRY_DISABLED === 'true' || process.env.DO_NOT_TRACK === '1'; const UNAUTHENTICATED_CONTEXT_METADATA = Object.freeze({ organizationId: 'anonymous' }); let client = null; let apiClient = null; const buildPostHogOkResponse = () => ({ status: 200, text: () => Promise.resolve(''), json: () => Promise.resolve({}), }); // PostHog logs flush failures directly; treat network issues as no-ops for CLI UX. // This will also make the retry mechanism ineffective. async function safePostHogFetch(url, options) { try { const response = await fetch(url, options); if (response.status < 200 || response.status >= 400) { if (process.env.DEBUG) { console.error(`PostHog request failed with status ${response.status}.`); } return buildPostHogOkResponse(); } return response; } catch (err) { if (process.env.DEBUG) { console.error('PostHog request failed.', err); } return buildPostHogOkResponse(); } } // Detect if running in a CI environment const isCI = () => { return Boolean(process.env.CI || // Travis CI, CircleCI, GitLab CI, GitHub Actions, etc. process.env.CONTINUOUS_INTEGRATION || process.env.BUILD_NUMBER || // Jenkins process.env.TEAMCITY_VERSION || // TeamCity process.env.GITLAB_CI || process.env.GITHUB_ACTIONS || process.env.BUILDKITE); }; /** * Check if the CLI is talking to Cosmo Cloud or a self-hosted instance */ const isTalkingToCosmoCloud = () => { const cloudUrl = 'https://cosmo-cp.wundergraph.com'; return config.baseURL.startsWith(cloudUrl); }; /** * Initialize PostHog client * This should be called once at the start of the CLI */ export const initTelemetry = () => { if (TELEMETRY_DISABLED) { return; } const posthogApiKey = process.env.POSTHOG_API_KEY || 'phc_CEnvoyw3KcTuC5E1seDPrgvAamgGRDLfzPi1e7RU1G1'; const posthogHost = process.env.POSTHOG_HOST || 'https://eu.i.posthog.com'; client = new PostHog(posthogApiKey, { host: posthogHost, flushAt: 1, // For CLI, we want to send events immediately flushInterval: 0, // Don't wait to flush events disableGeoip: false, fetch: safePostHogFetch, }); const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; apiClient = CreateClient({ baseUrl: config.baseURL, apiKey: config.apiKey, proxyUrl, }); // Handle errors silently to not interrupt CLI operations client.on('error', (err) => { if (process.env.DEBUG) { console.error('Telemetry error:', err); } }); }; /** * Generate a consistent distinct ID * Uses the platform API to get the organization slug if available */ const getIdentity = async () => { var _a; try { if (!apiClient) { return UNAUTHENTICATED_CONTEXT_METADATA; } if (!(await isAuthenticated())) { return UNAUTHENTICATED_CONTEXT_METADATA; } const resp = await apiClient.platform.whoAmI({}, { headers: getBaseHeaders(), }); if (((_a = resp.response) === null || _a === void 0 ? void 0 : _a.code) === EnumStatusCode.OK) { return { organizationId: resp.organizationId || 'anonymous', userEmail: resp.userEmail || undefined, }; } } catch (err) { // skip catch, returning anonymous identity if any error occurs (e.g. network issues, not logged in, etc.) if (process.env.DEBUG) { console.debug('Failed to get identity for telemetry, using anonymous.', err); } } return UNAUTHENTICATED_CONTEXT_METADATA; }; /** * Capture a usage event */ export const capture = async (eventName, properties = {}) => { var _a, _b; if (TELEMETRY_DISABLED || !client) { return; } try { const identity = await getIdentity(); const metadata = getMetadata(); client.capture({ distinctId: (_a = identity.userEmail) !== null && _a !== void 0 ? _a : identity.organizationId, groups: { cosmo_organization: (_b = identity.organizationId) !== null && _b !== void 0 ? _b : '', }, event: eventName, properties: { ...metadata, ...properties, }, }); } catch (err) { // Silently fail to not disrupt CLI operations if (process.env.DEBUG) { console.error('Failed to capture telemetry event:', err); } } }; /** * Capture a command failure event with error details */ export const captureCommandFailure = async (command, error) => { const errorMessage = error instanceof Error ? error.message : error; const errorStack = error instanceof Error ? error.stack : undefined; await capture('command_failure', { command, error_message: errorMessage, error_stack: errorStack, }); }; /** * Get CLI metadata to include with all events */ const getMetadata = () => { var _a; const machineId = crypto.hash('sha256', os.hostname(), 'hex'); return { cli_version: config.version, node_version: process.version, os_name: process.platform, os_version: ((_a = process.release) === null || _a === void 0 ? void 0 : _a.name) || '', platform: process.arch, machine_id: machineId, is_ci: isCI(), is_cosmo_cloud: isTalkingToCosmoCloud(), }; }; /** * Shutdown PostHog client - should be called before CLI exits */ export const shutdownTelemetry = async () => { if (client) { try { await client.shutdown(); } catch (err) { // Silently fail if (process.env.DEBUG) { console.error('Failed to shutdown telemetry:', err); } } } }; //# sourceMappingURL=telemetry.js.map