UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

344 lines (343 loc) 11.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Analytics = void 0; exports.createAnalytics = createAnalytics; exports.getGlobalAnalytics = getGlobalAnalytics; exports.setGlobalAnalytics = setGlobalAnalytics; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const crypto = __importStar(require("crypto")); const events_1 = require("events"); class Analytics extends events_1.EventEmitter { constructor(options = {}) { super(); this.events = []; this.metrics = []; this.timings = new Map(); this.enabled = options.enabled ?? this.isAnalyticsEnabled(); this.debug = options.debug ?? false; this.batchSize = options.batchSize ?? 100; this.flushInterval = options.flushInterval ?? 30000; // 30 seconds this.endpoint = options.endpoint; this.storageDir = options.storageDir ?? path.join(process.cwd(), '.re-shell', 'analytics'); this.anonymize = options.anonymize ?? true; this.customProperties = options.customProperties ?? {}; this.sessionId = this.generateSessionId(); this.userId = this.generateUserId(); if (this.enabled) { this.initialize(); } } initialize() { // Ensure storage directory exists fs.ensureDirSync(this.storageDir); // Load any pending events this.loadPendingEvents(); // Start flush timer this.startFlushTimer(); // Handle process exit process.on('exit', () => this.flush()); process.on('SIGINT', () => this.flush()); process.on('SIGTERM', () => this.flush()); } isAnalyticsEnabled() { // Check environment variables if (process.env.RESHELL_ANALYTICS === 'false') return false; if (process.env.CI === 'true') return false; if (process.env.NODE_ENV === 'test') return false; // Check user preference const configPath = path.join(process.cwd(), '.re-shell', 'config.json'); if (fs.existsSync(configPath)) { try { const config = fs.readJsonSync(configPath); return config.analytics?.enabled !== false; } catch { // Ignore errors } } return true; } generateSessionId() { return crypto.randomBytes(16).toString('hex'); } generateUserId() { if (this.anonymize) { // Generate anonymous user ID based on machine ID const machineId = this.getMachineId(); return crypto.createHash('sha256').update(machineId).digest('hex'); } // Use actual user ID if available const configPath = path.join(process.cwd(), '.re-shell', 'user.json'); if (fs.existsSync(configPath)) { try { const user = fs.readJsonSync(configPath); return user.id; } catch { // Ignore errors } } return undefined; } getMachineId() { // Simple machine ID based on hostname and platform const os = require('os'); return crypto.createHash('md5') .update(os.hostname() + os.platform() + os.arch()) .digest('hex'); } startFlushTimer() { this.flushTimer = setInterval(() => { if (this.events.length > 0 || this.metrics.length > 0) { this.flush(); } }, this.flushInterval); } track(event, properties) { if (!this.enabled) return; const analyticsEvent = { event, properties: { ...this.customProperties, ...properties }, timestamp: new Date(), sessionId: this.sessionId, userId: this.userId, version: this.getVersion(), platform: process.platform, nodeVersion: process.version }; this.events.push(analyticsEvent); this.emit('event', analyticsEvent); if (this.debug) { console.log('[Analytics] Event:', event, properties); } // Auto-flush if batch size reached if (this.events.length >= this.batchSize) { this.flush(); } } timeStart(label) { if (!this.enabled) return; this.timings.set(label, { label, startTime: Date.now() }); if (this.debug) { console.log('[Analytics] Timer started:', label); } } timeEnd(label) { if (!this.enabled) return; const timing = this.timings.get(label); if (!timing) { console.warn(`[Analytics] Timer ${label} not found`); return; } timing.endTime = Date.now(); timing.duration = timing.endTime - timing.startTime; // Track as event this.track('timing', { label, duration: timing.duration, unit: 'ms' }); // Clean up this.timings.delete(label); if (this.debug) { console.log('[Analytics] Timer ended:', label, timing.duration + 'ms'); } } increment(metric, value = 1, tags) { if (!this.enabled) return; const analyticsMetric = { name: metric, value, timestamp: new Date(), tags }; this.metrics.push(analyticsMetric); this.emit('metric', analyticsMetric); if (this.debug) { console.log('[Analytics] Metric:', metric, value, tags); } } flush() { if (!this.enabled) return; if (this.events.length === 0 && this.metrics.length === 0) return; const data = { events: [...this.events], metrics: [...this.metrics], sessionId: this.sessionId, timestamp: new Date() }; // Clear arrays this.events = []; this.metrics = []; // Save to disk this.saveToDisk(data); // Send to endpoint if configured if (this.endpoint) { this.sendToEndpoint(data); } this.emit('flush', data); if (this.debug) { console.log('[Analytics] Flushed:', data.events.length, 'events,', data.metrics.length, 'metrics'); } } saveToDisk(data) { try { const filename = `analytics-${Date.now()}.json`; const filepath = path.join(this.storageDir, filename); fs.writeJsonSync(filepath, data, { spaces: 2 }); // Clean up old files (keep last 100) this.cleanupOldFiles(); } catch (error) { if (this.debug) { console.error('[Analytics] Failed to save to disk:', error); } } } cleanupOldFiles() { try { const files = fs.readdirSync(this.storageDir) .filter(f => f.startsWith('analytics-') && f.endsWith('.json')) .sort(); if (files.length > 100) { const toDelete = files.slice(0, files.length - 100); for (const file of toDelete) { fs.unlinkSync(path.join(this.storageDir, file)); } } } catch (error) { if (this.debug) { console.error('[Analytics] Failed to cleanup old files:', error); } } } sendToEndpoint(data) { // TODO: Implement HTTP POST to analytics endpoint // This would typically send to a service like Segment, Mixpanel, etc. if (this.debug) { console.log('[Analytics] Would send to endpoint:', this.endpoint); } } loadPendingEvents() { try { const files = fs.readdirSync(this.storageDir) .filter(f => f.startsWith('analytics-') && f.endsWith('.json')); // TODO: Process pending files and send to endpoint if (this.debug && files.length > 0) { console.log('[Analytics] Found', files.length, 'pending analytics files'); } } catch (error) { if (this.debug) { console.error('[Analytics] Failed to load pending events:', error); } } } getVersion() { try { const packagePath = path.join(__dirname, '..', '..', 'package.json'); const pkg = fs.readJsonSync(packagePath); return pkg.version; } catch { return 'unknown'; } } disable() { this.enabled = false; if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = undefined; } } enable() { this.enabled = true; if (!this.flushTimer) { this.startFlushTimer(); } } isEnabled() { return this.enabled; } setUserId(userId) { this.userId = userId; } setCustomProperties(properties) { this.customProperties = { ...this.customProperties, ...properties }; } getSessionId() { return this.sessionId; } getUserId() { return this.userId; } } exports.Analytics = Analytics; // Global analytics instance let globalAnalytics = null; function createAnalytics(options) { return new Analytics(options); } function getGlobalAnalytics() { if (!globalAnalytics) { globalAnalytics = new Analytics(); } return globalAnalytics; } function setGlobalAnalytics(analytics) { if (globalAnalytics) { globalAnalytics.flush(); globalAnalytics.disable(); } globalAnalytics = analytics; }