@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
JavaScript
"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;
}