@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 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
  [![npm version](https://img.shields.io/npm/v/@eventra_dev/eventra-cli.svg)](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
8
9
  [![npm downloads](https://img.shields.io/npm/dm/@eventra_dev/eventra-cli.svg)](https://www.npmjs.com/package/@eventra_dev/eventra-cli)
9
10
  [![TypeScript](https://img.shields.io/badge/typescript-ready-blue.svg)](https://www.typescriptlang.org/)
10
- [![Tests](https://github.com/and-1991/eventra-cli/actions/workflows/release.yml/badge.svg)]()
11
11
  [![License](https://img.shields.io/npm/l/@eventra_dev/eventra-cli)]()
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
- ### track() detection
90
-
91
- ```ts
92
- tracker.track("feature_created")
93
- ```
52
+ Supports:
94
53
 
95
- ```ts
96
- track("user_signup")
97
- ```
54
+ - track("event")
55
+ - analytics.track("event")
56
+ - function wrappers
57
+ - component wrappers
58
+ - aliases
98
59
 
99
60
  ---
100
61
 
101
- ### Component wrappers
62
+ ## eventra check
102
63
 
103
- ```tsx
104
- <TrackedButton event="feature_created" />
105
- ```
64
+ Validate events without modifying config.
106
65
 
107
- ```tsx
108
- <MyComponent event="user_signup" />
66
+ ```bash
67
+ eventra check
109
68
  ```
110
69
 
111
- ---
70
+ ### Detects:
112
71
 
113
- ### Function wrappers
72
+ - New events
73
+ - Unresolved dynamic events
114
74
 
115
- ```ts
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
- #### 1. New events
87
+ ## eventra check --fix
144
88
 
145
- If your code contains events not listed in `eventra.json`:
89
+ Interactive auto-fix mode.
146
90
 
147
- ``` ts
148
- trackFeature("new_event")
91
+ ```bash
92
+ eventra check --fix
149
93
  ```
150
94
 
151
- CLI output:
95
+ ### What it does:
152
96
 
153
- New events detected:
154
- + new_event
155
- ❌ Event check failed
97
+ - Adds new events to config
98
+ - Resolves dynamic events via alias
99
+ - Allows skipping dynamic values
156
100
 
157
- ------------------------------------------------------------------------
101
+ ---
158
102
 
159
- #### 2. Unresolved dynamic events
103
+ ## eventra watch
160
104
 
161
- If your code contains dynamic values without alias:
105
+ Real-time event detection.
162
106
 
163
- ``` ts
164
- trackFeature(eventName)
107
+ ```bash
108
+ eventra watch
165
109
  ```
166
110
 
167
- CLI output:
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
- ### CI usage
118
+ ---
176
119
 
177
- ``` bash
178
- pnpm eventra check
179
- ```
120
+ ## eventra send
180
121
 
181
- #### GitHub Actions example
122
+ Send events to backend.
182
123
 
183
- ``` yaml
184
- - run: pnpm eventra check
124
+ ```bash
125
+ eventra send
185
126
  ```
186
127
 
187
- ------------------------------------------------------------------------
128
+ ---
188
129
 
189
- ### Skipping dynamic events
130
+ # Configuration
190
131
 
191
- You can explicitly ignore dynamic variables using aliases:
132
+ Example `eventra.json`:
192
133
 
193
- ``` json
134
+ ```json
194
135
  {
195
- "aliases": {
196
- "eventName": "__skip__"
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
- ### When to use
150
+ # Key Concepts
204
151
 
205
- Use `eventra check` to:
152
+ ### Aliases
206
153
 
207
- - enforce event consistency\
208
- - prevent accidental event creation\
209
- - validate analytics in pull requests
154
+ ```ts
155
+ const EVENT = "user_click";
156
+ track(EVENT);
157
+ ```
210
158
 
211
- ---
159
+ Auto-resolved → `user_click`
212
160
 
213
- ## eventra send
161
+ ---
214
162
 
215
- Send events to Eventra backend.
163
+ ### Dynamic events
216
164
 
217
- ```bash
218
- eventra send
165
+ ```ts
166
+ track(eventName);
219
167
  ```
220
168
 
221
- New events are queued for processing and will appear in dashboard shortly.
222
-
223
- Processing typically takes:
169
+ Must be:
224
170
 
225
- ~1-2 minutes
226
-
227
- This delay ensures reliable event ingestion and aggregation.
171
+ - aliased
172
+ - or skipped (`__skip__`)
228
173
 
229
174
  ---
230
175
 
231
- # Configuration
232
-
233
- Eventra CLI creates `eventra.json`
176
+ ### File tracking
234
177
 
235
- Example:
178
+ All events include:
236
179
 
237
- ```json
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
 
@@ -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 Set();
30
- const newEvents = new Set();
31
- const unresolvedDynamic = new Set();
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 virtualFile = parser === "ts" ? file : file + ".tsx";
52
- const source = project.createSourceFile(virtualFile, content, { overwrite: true });
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
- // alias skip
63
- if (aliases[value] === "__skip__") {
62
+ // skip
63
+ if (aliases[value] === "__skip__")
64
64
  continue;
65
- }
66
65
  // alias resolve
67
- if (aliases[value]) {
68
- foundEvents.add(aliases[value]);
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.add(value);
74
+ unresolvedDynamic.set(value, event);
74
75
  continue;
75
76
  }
76
- // static
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 file (error): ${file}`));
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
- // find new events
87
- for (const event of foundEvents) {
88
- if (!knownEvents.has(event)) {
89
- newEvents.add(event);
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
- // dynamic
94
- if (unresolvedDynamic.size > 0) {
138
+ if (unresolvedDynamic.size) {
95
139
  hasError = true;
96
- console.log(chalk_1.default.red("\nUnresolved dynamic events:"));
97
- [...unresolvedDynamic]
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
- // new events
102
- if (newEvents.size > 0) {
143
+ if (newEvents.size) {
103
144
  hasError = true;
104
- console.log(chalk_1.default.red("\nNew events detected:"));
105
- [...newEvents]
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 events are valid"));
152
+ console.log(chalk_1.default.green("\nAll good"));
116
153
  }