/
index.ts
149 lines (126 loc) Β· 3.85 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import exitHook from 'async-exit-hook';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'node:fs';
import { machineIdSync } from 'node-machine-id';
import * as child_process from 'node:child_process';
export interface CommandExecutionEvent {
eventType: string;
// Identifiers
cliId: string;
machineId: string; // Hash of OS-native UUID/GUID
// Flags
flags: string[];
// Status
exitCode: number;
// CLI version
version?: string;
// Timing
durationMs: number;
unixTimestampMs: number;
}
export interface ConsoleCatOptions {
cliId: string; // Console Cat ID for the CLI.
version?: string;
apiUrl?: string; // URL for Console Cat API.
debug?: boolean; // Print information for debugging purposes.
}
const DEFAULT_API_URL = 'https://api.consolecat.dev';
const fetchScript = (
url: string,
events: object[]
) => `const fetch = require('node-fetch');
fetch('${url}', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: '{ \\"events\\": ${JSON.stringify(events).replaceAll(`"`, `\\"`)} }',
}).then(response => console.log(response.ok));
`;
export class ConsoleCat {
static eventsFilePath(options: ConsoleCatOptions) {
const tmpDir = path.join(os.tmpdir(), options.cliId);
return path.join(tmpDir, 'events.jsonl');
}
static export(options: ConsoleCatOptions, done?: () => void) {
try {
const tmpDir = path.join(os.tmpdir(), options.cliId);
const eventsFilePath = path.join(tmpDir, 'events.jsonl');
if (!fs.existsSync(eventsFilePath)) {
if (options.debug) {
}
return;
}
const apiUrl = options.apiUrl ?? DEFAULT_API_URL;
const events = fs
.readFileSync(eventsFilePath, 'utf-8')
.split('\n')
.filter(event => event !== '')
.map(event => JSON.parse(event));
const url = apiUrl + '/events';
// Send telemetry to Console Cat
const buffer = child_process
.execSync(`echo "${fetchScript(url, events)}" | node`)
.toString();
if (buffer.includes('true')) {
fs.unlinkSync(eventsFilePath);
if (options.debug) {
console.log(`Successfully flushed ${events.length} events`);
}
}
done?.();
} catch (e) {
if (options.debug) console.log(e);
done?.();
}
}
static initialize(options: ConsoleCatOptions) {
if (process.env.DO_NOT_TRACK && process.env.DO_NOT_TRACK !== '0') {
// Follow Console Do Not Track (DNT)
return;
}
if (!options.cliId) {
throw new Error('Missing CLI ID for Console Cat!');
}
const startUnixTimestampMs = Date.now();
const executionEventStart = {
eventType: 'command_execution',
cliId: options.cliId,
machineId: machineIdSync(),
version: options.version,
};
exitHook(done => {
try {
const executionEvent: CommandExecutionEvent = {
...executionEventStart,
flags: process.argv.filter(arg => arg.startsWith('-')),
unixTimestampMs: startUnixTimestampMs,
durationMs: Date.now() - startUnixTimestampMs,
exitCode: process.exitCode ?? 0,
};
const tmpDir = path.join(os.tmpdir(), options.cliId);
const eventsFilePath = path.join(tmpDir, 'events.jsonl');
if (options.debug) {
console.debug(JSON.stringify(executionEvent, null, 4));
console.debug(eventsFilePath);
}
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir);
}
// Add latest event to buffer
fs.appendFileSync(
eventsFilePath,
JSON.stringify(executionEvent) + '\n',
{}
);
// Send telemetry to Console Cat
this.export(options, done);
} catch (e) {
if (options.debug) console.log(e);
done?.();
}
});
}
}