@eventra_dev/eventra-cli 0.0.18 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -166
- package/dist/commands/check.js +80 -43
- package/dist/commands/sync.js +93 -53
- package/dist/commands/watch.js +97 -0
- package/dist/index.js +10 -6
- package/dist/utils/detect-aliases.js +39 -0
- package/dist/utils/detect-wrappers.js +84 -0
- package/dist/utils/extract.js +18 -98
- package/dist/utils/scanners/component-wrappers.js +23 -20
- package/dist/utils/scanners/function-wrappers.js +16 -18
- package/dist/utils/scanners/track.js +20 -8
- package/package.json +2 -1
- package/src/commands/check.ts +100 -75
- package/src/commands/sync.ts +170 -110
- package/src/commands/watch.ts +113 -0
- package/src/index.ts +11 -6
- package/src/types.ts +4 -0
- package/src/utils/detect-aliases.ts +46 -0
- package/src/utils/detect-wrappers.ts +119 -0
- package/src/utils/extract.ts +20 -133
- package/src/utils/scanners/component-wrappers.ts +27 -25
- package/src/utils/scanners/function-wrappers.ts +47 -90
- package/src/utils/scanners/track.ts +23 -8
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
<p align="center">
|
|
2
3
|
<img src="./assets/eventra-icon-animated.svg" width="120">
|
|
3
4
|
</p>
|
|
@@ -7,56 +8,22 @@
|
|
|
7
8
|
[](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
|
|
8
9
|
[](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
|
|
9
10
|
[](https://www.typescriptlang.org/)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
12
|
|
|
13
13
|
Eventra CLI automatically discovers feature usage events in your codebase and syncs them with Eventra.
|
|
14
14
|
|
|
15
|
-
Eventra CLI helps you:
|
|
16
|
-
|
|
17
|
-
- Discover feature usage automatically
|
|
18
|
-
- Detect wrapper components
|
|
19
|
-
- Detect wrapper functions
|
|
20
|
-
- Keep events in sync
|
|
21
|
-
- Register features in Eventra
|
|
22
|
-
- Maintain consistent event naming
|
|
23
|
-
|
|
24
|
-
It is designed to be:
|
|
25
|
-
|
|
26
|
-
- framework-agnostic
|
|
27
|
-
- static analysis based
|
|
28
|
-
- zero runtime overhead
|
|
29
|
-
- production-safe
|
|
30
|
-
- backend + frontend compatible
|
|
31
|
-
|
|
32
15
|
---
|
|
33
16
|
|
|
34
17
|
# Installation
|
|
35
18
|
|
|
36
|
-
### npm
|
|
37
|
-
|
|
38
19
|
```bash
|
|
39
20
|
npm install -D @eventra_dev/eventra-cli
|
|
40
21
|
```
|
|
41
22
|
|
|
42
|
-
### pnpm
|
|
43
|
-
|
|
44
23
|
```bash
|
|
45
24
|
pnpm add -D @eventra_dev/eventra-cli
|
|
46
25
|
```
|
|
47
26
|
|
|
48
|
-
### yarn
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
yarn add -D @eventra_dev/eventra-cli
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### npx
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npx eventra init
|
|
58
|
-
```
|
|
59
|
-
|
|
60
27
|
---
|
|
61
28
|
|
|
62
29
|
# Quick Start
|
|
@@ -76,180 +43,142 @@ eventra send
|
|
|
76
43
|
|
|
77
44
|
Creates `eventra.json` configuration file.
|
|
78
45
|
|
|
79
|
-
```bash
|
|
80
|
-
eventra init
|
|
81
|
-
```
|
|
82
|
-
|
|
83
46
|
---
|
|
84
47
|
|
|
85
48
|
## eventra sync
|
|
86
49
|
|
|
87
50
|
Scans your project and discovers events automatically.
|
|
88
51
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
tracker.track("feature_created")
|
|
93
|
-
```
|
|
52
|
+
Supports:
|
|
94
53
|
|
|
95
|
-
|
|
96
|
-
track("
|
|
97
|
-
|
|
54
|
+
- track("event")
|
|
55
|
+
- analytics.track("event")
|
|
56
|
+
- function wrappers
|
|
57
|
+
- component wrappers
|
|
58
|
+
- aliases
|
|
98
59
|
|
|
99
60
|
---
|
|
100
61
|
|
|
101
|
-
|
|
62
|
+
## eventra check
|
|
102
63
|
|
|
103
|
-
|
|
104
|
-
<TrackedButton event="feature_created" />
|
|
105
|
-
```
|
|
64
|
+
Validate events without modifying config.
|
|
106
65
|
|
|
107
|
-
```
|
|
108
|
-
|
|
66
|
+
```bash
|
|
67
|
+
eventra check
|
|
109
68
|
```
|
|
110
69
|
|
|
111
|
-
|
|
70
|
+
### Detects:
|
|
112
71
|
|
|
113
|
-
|
|
72
|
+
- New events
|
|
73
|
+
- Unresolved dynamic events
|
|
114
74
|
|
|
115
|
-
|
|
116
|
-
trackFeature("feature_created")
|
|
117
|
-
```
|
|
75
|
+
### Output example:
|
|
118
76
|
|
|
119
|
-
```ts
|
|
120
|
-
analytics.trackFeature("user_signup")
|
|
121
77
|
```
|
|
78
|
+
New events:
|
|
79
|
+
+ user_click (src/Button.tsx:42)
|
|
122
80
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
## eventra check
|
|
126
|
-
|
|
127
|
-
Validate events in your codebase without modifying `eventra.json`.
|
|
128
|
-
|
|
129
|
-
This command is designed for CI/CD pipelines and ensures:
|
|
130
|
-
|
|
131
|
-
- No new events are introduced unintentionally
|
|
132
|
-
- All dynamic events are resolved via aliases
|
|
133
|
-
- Event naming stays consistent
|
|
134
|
-
|
|
135
|
-
``` bash
|
|
136
|
-
eventra check
|
|
81
|
+
Dynamic:
|
|
82
|
+
- eventName (src/App.tsx:10)
|
|
137
83
|
```
|
|
138
84
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
### What it checks
|
|
85
|
+
---
|
|
142
86
|
|
|
143
|
-
|
|
87
|
+
## eventra check --fix
|
|
144
88
|
|
|
145
|
-
|
|
89
|
+
Interactive auto-fix mode.
|
|
146
90
|
|
|
147
|
-
```
|
|
148
|
-
|
|
91
|
+
```bash
|
|
92
|
+
eventra check --fix
|
|
149
93
|
```
|
|
150
94
|
|
|
151
|
-
|
|
95
|
+
### What it does:
|
|
152
96
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
97
|
+
- Adds new events to config
|
|
98
|
+
- Resolves dynamic events via alias
|
|
99
|
+
- Allows skipping dynamic values
|
|
156
100
|
|
|
157
|
-
|
|
101
|
+
---
|
|
158
102
|
|
|
159
|
-
|
|
103
|
+
## eventra watch
|
|
160
104
|
|
|
161
|
-
|
|
105
|
+
Real-time event detection.
|
|
162
106
|
|
|
163
|
-
```
|
|
164
|
-
|
|
107
|
+
```bash
|
|
108
|
+
eventra watch
|
|
165
109
|
```
|
|
166
110
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Unresolved dynamic events:
|
|
170
|
-
- eventName
|
|
171
|
-
❌ Event check failed
|
|
111
|
+
### Features:
|
|
172
112
|
|
|
173
|
-
|
|
113
|
+
- Detects new events instantly
|
|
114
|
+
- Debounced updates
|
|
115
|
+
- No duplicates
|
|
116
|
+
- Ignores temp files
|
|
174
117
|
|
|
175
|
-
|
|
118
|
+
---
|
|
176
119
|
|
|
177
|
-
|
|
178
|
-
pnpm eventra check
|
|
179
|
-
```
|
|
120
|
+
## eventra send
|
|
180
121
|
|
|
181
|
-
|
|
122
|
+
Send events to backend.
|
|
182
123
|
|
|
183
|
-
```
|
|
184
|
-
|
|
124
|
+
```bash
|
|
125
|
+
eventra send
|
|
185
126
|
```
|
|
186
127
|
|
|
187
|
-
|
|
128
|
+
---
|
|
188
129
|
|
|
189
|
-
|
|
130
|
+
# Configuration
|
|
190
131
|
|
|
191
|
-
|
|
132
|
+
Example `eventra.json`:
|
|
192
133
|
|
|
193
|
-
```
|
|
134
|
+
```json
|
|
194
135
|
{
|
|
195
|
-
"
|
|
196
|
-
|
|
136
|
+
"apiKey": "",
|
|
137
|
+
"events": [],
|
|
138
|
+
"wrappers": [],
|
|
139
|
+
"functionWrappers": [],
|
|
140
|
+
"aliases": {},
|
|
141
|
+
"sync": {
|
|
142
|
+
"include": ["**/*.{ts,tsx,js,jsx,vue,svelte,astro}"],
|
|
143
|
+
"exclude": ["node_modules", "dist", ".next", ".git"]
|
|
197
144
|
}
|
|
198
145
|
}
|
|
199
146
|
```
|
|
200
147
|
|
|
201
|
-
|
|
148
|
+
---
|
|
202
149
|
|
|
203
|
-
|
|
150
|
+
# Key Concepts
|
|
204
151
|
|
|
205
|
-
|
|
152
|
+
### Aliases
|
|
206
153
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
154
|
+
```ts
|
|
155
|
+
const EVENT = "user_click";
|
|
156
|
+
track(EVENT);
|
|
157
|
+
```
|
|
210
158
|
|
|
211
|
-
|
|
159
|
+
Auto-resolved → `user_click`
|
|
212
160
|
|
|
213
|
-
|
|
161
|
+
---
|
|
214
162
|
|
|
215
|
-
|
|
163
|
+
### Dynamic events
|
|
216
164
|
|
|
217
|
-
```
|
|
218
|
-
|
|
165
|
+
```ts
|
|
166
|
+
track(eventName);
|
|
219
167
|
```
|
|
220
168
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
Processing typically takes:
|
|
169
|
+
Must be:
|
|
224
170
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
This delay ensures reliable event ingestion and aggregation.
|
|
171
|
+
- aliased
|
|
172
|
+
- or skipped (`__skip__`)
|
|
228
173
|
|
|
229
174
|
---
|
|
230
175
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Eventra CLI creates `eventra.json`
|
|
176
|
+
### File tracking
|
|
234
177
|
|
|
235
|
-
|
|
178
|
+
All events include:
|
|
236
179
|
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
"apiKey": "",
|
|
240
|
-
"events": [],
|
|
241
|
-
"wrappers": [],
|
|
242
|
-
"functionWrappers": [],
|
|
243
|
-
"sync": {
|
|
244
|
-
"include": ["**/*.{ts,tsx,js,jsx}"],
|
|
245
|
-
"exclude": [
|
|
246
|
-
"node_modules",
|
|
247
|
-
"dist",
|
|
248
|
-
".next",
|
|
249
|
-
".git"
|
|
250
|
-
]
|
|
251
|
-
}
|
|
252
|
-
}
|
|
180
|
+
```
|
|
181
|
+
event_name (file:line)
|
|
253
182
|
```
|
|
254
183
|
|
|
255
184
|
---
|
|
@@ -259,9 +188,7 @@ Example:
|
|
|
259
188
|
Frontend:
|
|
260
189
|
|
|
261
190
|
- React
|
|
262
|
-
- Next.js
|
|
263
191
|
- Vue
|
|
264
|
-
- Nuxt
|
|
265
192
|
- Svelte
|
|
266
193
|
- Astro
|
|
267
194
|
|
|
@@ -271,31 +198,12 @@ Backend:
|
|
|
271
198
|
- Express
|
|
272
199
|
- NestJS
|
|
273
200
|
- Fastify
|
|
274
|
-
- Hono
|
|
275
|
-
- Bun
|
|
276
|
-
- Deno
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
# How It Works
|
|
281
|
-
|
|
282
|
-
Eventra CLI:
|
|
283
|
-
|
|
284
|
-
1. Scans your codebase
|
|
285
|
-
2. Detects tracking calls
|
|
286
|
-
3. Detects wrapper components
|
|
287
|
-
4. Detects wrapper functions
|
|
288
|
-
5. Syncs discovered events
|
|
289
|
-
6. Registers events in Eventra
|
|
290
|
-
|
|
291
|
-
No runtime SDK required.
|
|
292
201
|
|
|
293
202
|
---
|
|
294
203
|
|
|
295
204
|
# Requirements
|
|
296
205
|
|
|
297
206
|
- Node.js 18+
|
|
298
|
-
- JavaScript / TypeScript project
|
|
299
207
|
|
|
300
208
|
---
|
|
301
209
|
|
package/dist/commands/check.js
CHANGED
|
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
9
9
|
const ts_morph_1 = require("ts-morph");
|
|
10
10
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
12
|
const config_1 = require("../utils/config");
|
|
12
13
|
const router_1 = require("../utils/parsers/router");
|
|
13
14
|
const vue_1 = require("../utils/parsers/vue");
|
|
@@ -16,7 +17,7 @@ const astro_1 = require("../utils/parsers/astro");
|
|
|
16
17
|
const track_1 = require("../utils/scanners/track");
|
|
17
18
|
const function_wrappers_1 = require("../utils/scanners/function-wrappers");
|
|
18
19
|
const component_wrappers_1 = require("../utils/scanners/component-wrappers");
|
|
19
|
-
async function check() {
|
|
20
|
+
async function check({ fix = false }) {
|
|
20
21
|
const config = await (0, config_1.loadConfig)();
|
|
21
22
|
if (!config) {
|
|
22
23
|
console.log(chalk_1.default.red("eventra.json not found"));
|
|
@@ -25,19 +26,17 @@ async function check() {
|
|
|
25
26
|
console.log(chalk_1.default.blue("Checking events..."));
|
|
26
27
|
const project = new ts_morph_1.Project();
|
|
27
28
|
const knownEvents = new Set(config.events ?? []);
|
|
28
|
-
const aliases = config.aliases ?? {};
|
|
29
|
-
const foundEvents = new
|
|
30
|
-
const newEvents = new
|
|
31
|
-
const unresolvedDynamic = new
|
|
29
|
+
const aliases = { ...(config.aliases ?? {}) };
|
|
30
|
+
const foundEvents = new Map();
|
|
31
|
+
const newEvents = new Map();
|
|
32
|
+
const unresolvedDynamic = new Map();
|
|
32
33
|
const files = await (0, fast_glob_1.default)(config.sync.include, {
|
|
33
34
|
ignore: config.sync.exclude
|
|
34
35
|
});
|
|
35
36
|
for (const file of files) {
|
|
36
37
|
try {
|
|
37
|
-
// skip huge files
|
|
38
38
|
const stat = await promises_1.default.stat(file);
|
|
39
39
|
if (stat.size > 2 * 1024 * 1024) {
|
|
40
|
-
console.log(chalk_1.default.gray(`Skipped large file: ${file}`));
|
|
41
40
|
continue;
|
|
42
41
|
}
|
|
43
42
|
const parser = (0, router_1.detectParser)(file);
|
|
@@ -48,69 +47,107 @@ async function check() {
|
|
|
48
47
|
content = (0, svelte_1.parseSvelte)(content);
|
|
49
48
|
if (parser === "astro")
|
|
50
49
|
content = (0, astro_1.parseAstro)(content);
|
|
51
|
-
const
|
|
52
|
-
|
|
50
|
+
const source = project.createSourceFile(file, content, {
|
|
51
|
+
overwrite: true
|
|
52
|
+
});
|
|
53
53
|
const found = [
|
|
54
|
-
...(0, track_1.scanTrack)(source, config),
|
|
55
|
-
...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? []),
|
|
56
|
-
...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [])
|
|
54
|
+
...(0, track_1.scanTrack)(source, config, aliases),
|
|
55
|
+
...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? [], aliases),
|
|
56
|
+
...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [], aliases)
|
|
57
57
|
];
|
|
58
58
|
for (const event of found) {
|
|
59
59
|
const { value, dynamic } = event;
|
|
60
60
|
if (!value)
|
|
61
61
|
continue;
|
|
62
|
-
//
|
|
63
|
-
if (aliases[value] === "__skip__")
|
|
62
|
+
// skip
|
|
63
|
+
if (aliases[value] === "__skip__")
|
|
64
64
|
continue;
|
|
65
|
-
}
|
|
66
65
|
// alias resolve
|
|
67
|
-
if (aliases
|
|
68
|
-
foundEvents.
|
|
66
|
+
if (value in aliases) {
|
|
67
|
+
foundEvents.set(aliases[value], {
|
|
68
|
+
...event,
|
|
69
|
+
value: aliases[value]
|
|
70
|
+
});
|
|
69
71
|
continue;
|
|
70
72
|
}
|
|
71
|
-
// dynamic without alias
|
|
72
73
|
if (dynamic) {
|
|
73
|
-
unresolvedDynamic.
|
|
74
|
+
unresolvedDynamic.set(value, event);
|
|
74
75
|
continue;
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
-
foundEvents.add(value);
|
|
77
|
+
foundEvents.set(value, event);
|
|
78
78
|
}
|
|
79
|
-
// clear memory
|
|
80
79
|
project.removeSourceFile(source);
|
|
81
80
|
}
|
|
82
81
|
catch {
|
|
83
|
-
console.log(chalk_1.default.gray(`Skipped
|
|
82
|
+
console.log(chalk_1.default.gray(`Skipped: ${file}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// find new
|
|
86
|
+
for (const [key, event] of foundEvents) {
|
|
87
|
+
if (!knownEvents.has(key)) {
|
|
88
|
+
newEvents.set(key, event);
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
// FIX MODE
|
|
92
|
+
if (fix) {
|
|
93
|
+
console.log(chalk_1.default.yellow("\nFix mode\n"));
|
|
94
|
+
// add new events
|
|
95
|
+
for (const [name] of newEvents) {
|
|
96
|
+
config.events.push(name);
|
|
97
|
+
console.log(chalk_1.default.green(`+ ${name}`));
|
|
98
|
+
}
|
|
99
|
+
// resolve dynamic
|
|
100
|
+
for (const [value] of unresolvedDynamic) {
|
|
101
|
+
const { action } = await inquirer_1.default.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "list",
|
|
104
|
+
name: "action",
|
|
105
|
+
message: `Dynamic: ${value}`,
|
|
106
|
+
choices: [
|
|
107
|
+
{ name: "Alias", value: "alias" },
|
|
108
|
+
{ name: "Skip", value: "skip" }
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
]);
|
|
112
|
+
if (action === "skip") {
|
|
113
|
+
aliases[value] = "__skip__";
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const { name } = await inquirer_1.default.prompt([
|
|
117
|
+
{
|
|
118
|
+
type: "input",
|
|
119
|
+
name: "name",
|
|
120
|
+
message: "Event name:"
|
|
121
|
+
}
|
|
122
|
+
]);
|
|
123
|
+
const normalized = name.trim().toLowerCase();
|
|
124
|
+
if (!normalized)
|
|
125
|
+
continue;
|
|
126
|
+
aliases[value] = normalized;
|
|
127
|
+
config.events.push(normalized);
|
|
128
|
+
console.log(chalk_1.default.green(`${value} → ${normalized}`));
|
|
90
129
|
}
|
|
130
|
+
config.aliases = Object.fromEntries(Object.entries(aliases).sort());
|
|
131
|
+
config.events = [...new Set(config.events)].sort();
|
|
132
|
+
await (0, config_1.saveConfig)(config);
|
|
133
|
+
console.log(chalk_1.default.green("\n✔ Fixed"));
|
|
134
|
+
return;
|
|
91
135
|
}
|
|
136
|
+
// NORMAL MODE
|
|
92
137
|
let hasError = false;
|
|
93
|
-
|
|
94
|
-
if (unresolvedDynamic.size > 0) {
|
|
138
|
+
if (unresolvedDynamic.size) {
|
|
95
139
|
hasError = true;
|
|
96
|
-
console.log(chalk_1.default.red("\
|
|
97
|
-
|
|
98
|
-
.sort()
|
|
99
|
-
.forEach((e) => console.log(chalk_1.default.red(`- ${e}`)));
|
|
140
|
+
console.log(chalk_1.default.red("\nDynamic events:"));
|
|
141
|
+
unresolvedDynamic.forEach((e) => console.log(chalk_1.default.red(`- ${e.value} (${e.file}:${e.line})`)));
|
|
100
142
|
}
|
|
101
|
-
|
|
102
|
-
if (newEvents.size > 0) {
|
|
143
|
+
if (newEvents.size) {
|
|
103
144
|
hasError = true;
|
|
104
|
-
console.log(chalk_1.default.red("\nNew events
|
|
105
|
-
|
|
106
|
-
.sort()
|
|
107
|
-
.forEach((e) => console.log(chalk_1.default.red(`+ ${e}`)));
|
|
145
|
+
console.log(chalk_1.default.red("\nNew events:"));
|
|
146
|
+
newEvents.forEach((e) => console.log(chalk_1.default.red(`+ ${e.value} (${e.file}:${e.line})`)));
|
|
108
147
|
}
|
|
109
148
|
if (hasError) {
|
|
110
|
-
console.log("");
|
|
111
|
-
console.log(chalk_1.default.red("Event check failed"));
|
|
112
|
-
console.log(chalk_1.default.red(`dynamic: ${unresolvedDynamic.size}, new: ${newEvents.size}`));
|
|
149
|
+
console.log(chalk_1.default.red("\nEvent check failed"));
|
|
113
150
|
process.exit(1);
|
|
114
151
|
}
|
|
115
|
-
console.log(chalk_1.default.green("\nAll
|
|
152
|
+
console.log(chalk_1.default.green("\nAll good"));
|
|
116
153
|
}
|