@eventra_dev/eventra-cli 0.3.9 → 0.3.11

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
@@ -9,17 +9,15 @@
9
9
  [![TypeScript](https://img.shields.io/badge/typescript-ready-blue.svg)](https://www.typescriptlang.org/)
10
10
  [![License](https://img.shields.io/npm/l/@eventra_dev/eventra-cli)]()
11
11
 
12
- Eventra CLI automatically discovers and tracks event usage across your codebase — even through abstractions like wrappers, propagation chains, variables, and cross-file calls.
12
+ Eventra CLI statically discovers analytics events from [**@eventra_dev/eventra-sdk**](https://www.npmjs.com/package/@eventra_dev/eventra-sdk) usage in your codebase — including function wrappers and cross-file propagation chains.
13
13
 
14
14
  ---
15
15
 
16
16
  ## Overview
17
17
 
18
- Eventra scans your JavaScript and TypeScript codebase using a TypeScript-powered semantic engine and extracts analytics event usage statically.
18
+ The CLI scans TypeScript and JavaScript with the TypeScript compiler API and extracts **only** calls to `Eventra.prototype.track()` on instances of `Eventra` imported from `@eventra_dev/eventra-sdk`.
19
19
 
20
- It supports semantic parameter propagation, return propagation, cross-file resolution, and incremental dependency-aware analysis.
21
-
22
- No runtime instrumentation required.
20
+ It does **not** detect generic `track()`, Segment, Google Analytics, or other libraries.
23
21
 
24
22
  ---
25
23
 
@@ -31,6 +29,12 @@ npm install -D @eventra_dev/eventra-cli
31
29
  pnpm add -D @eventra_dev/eventra-cli
32
30
  ```
33
31
 
32
+ Your project should use the runtime SDK:
33
+
34
+ ```bash
35
+ pnpm add @eventra_dev/eventra-sdk
36
+ ```
37
+
34
38
  ---
35
39
 
36
40
  ## Quick Start
@@ -44,180 +48,108 @@ eventra send
44
48
 
45
49
  ---
46
50
 
47
- ## How it works
48
-
49
- 1. Parses your project using the TypeScript compiler API
50
- 2. Builds semantic indexes from AST nodes
51
- 3. Resolves wrappers, imports, exports, and propagation chains
52
- 4. Extracts concrete analytics events statically
53
- 5. Incrementally re-analyzes only affected files
54
-
55
- No runtime SDK hooks.
56
- No Babel transforms.
57
- No instrumentation.
58
-
59
- ---
60
-
61
- ## Why Eventra
62
-
63
- * Prevent missing or inconsistent event tracking
64
- * Keep analytics schemas synchronized with source code
65
- * Detect dead or unused events
66
- * Analyze wrapper-heavy codebases statically
67
- * Integrate validation into CI/CD pipelines
68
- * Avoid runtime analytics overhead
69
-
70
- ---
71
-
72
- ## Commands
73
-
74
- ### `eventra init`
75
-
76
- Creates `eventra.json` configuration file.
77
-
78
- ---
79
-
80
- ### `eventra sync`
81
-
82
- Scans your project and updates the list of detected events.
51
+ ## What gets detected
83
52
 
84
- #### Supports
85
-
86
- ##### Direct calls
53
+ ### Direct SDK calls
87
54
 
88
55
  ```ts
89
- track("event")
90
- analytics.track("event")
91
- ```
56
+ import { Eventra } from "@eventra_dev/eventra-sdk";
92
57
 
93
- ##### Variables
58
+ const tracker = new Eventra({ apiKey: "YOUR_PROJECT_API_KEY" });
94
59
 
95
- ```ts
96
- const EVENT = "click"
97
- track(EVENT)
60
+ tracker.track("checkout.completed");
61
+ tracker.track("app.loaded", { userId: "user_123" });
62
+ tracker?.track("optional.chain");
98
63
  ```
99
64
 
100
- ##### Enums
101
-
102
- ```ts
103
- track(EVENTS.CLICK)
104
- ```
65
+ ### Function wrappers (propagation)
105
66
 
106
- ##### Template strings
67
+ Wrappers that call `Eventra.track()` inside are registered automatically:
107
68
 
108
69
  ```ts
109
- track(`click_${type}`)
110
- ```
111
-
112
- ##### Conditional expressions
70
+ function trackFeature(name: string) {
71
+ sdk.track(name);
72
+ }
113
73
 
114
- ```ts
115
- track(condition ? "a" : "b")
74
+ trackFeature("purchase");
116
75
  ```
117
76
 
118
- ##### Arrays
77
+ ### Variables, templates, conditionals
119
78
 
120
79
  ```ts
121
- track(["a", "b"])
122
- ```
80
+ const EVENT = "signup";
81
+ tracker.track(EVENT);
123
82
 
124
- ##### Object payloads
83
+ tracker.track(`feature_${type}`);
125
84
 
126
- ```ts
127
- track({
128
- event: "click"
129
- })
85
+ tracker.track(flag ? "path.a" : "path.b");
130
86
  ```
131
87
 
132
- ##### Function wrappers
88
+ ### Cross-file
133
89
 
134
90
  ```ts
135
- function trackFeature(name: string) {
136
- track(name)
91
+ // tracker.ts
92
+ export function trackFeature(name: string) {
93
+ client.track(name);
137
94
  }
138
95
 
139
- trackFeature("click")
96
+ // app.ts
97
+ import { trackFeature } from "./tracker";
98
+ trackFeature("purchase");
140
99
  ```
141
100
 
142
- ##### Semantic parameter propagation
101
+ ---
143
102
 
144
- ```ts
145
- function trackEvent(props: {
146
- event: string
147
- }) {
148
- track(props.event)
149
- }
103
+ ## Event name rules (aligned with SDK)
150
104
 
151
- trackEvent({
152
- event: "purchase"
153
- })
154
- ```
105
+ | Rule | Limit |
106
+ |------|--------|
107
+ | Max length | **64 characters** (same as SDK) |
108
+ | Allowed characters | `a-zA-Z0-9:_./-` |
109
+ | Position | First argument of `.track(name, options?)` |
155
110
 
156
- ##### Return propagation
111
+ The second argument (`userId`, `properties`) is **not** used as the event name.
157
112
 
158
- ```ts
159
- function buildEvent(name: string) {
160
- return name
161
- }
162
-
163
- track(buildEvent("signup"))
164
- ```
113
+ ---
165
114
 
166
- ##### Cross-file resolution
115
+ ## What is ignored
167
116
 
168
117
  ```ts
169
- import { trackFeature } from "./tracker"
118
+ // Not from @eventra_dev/eventra-sdk
119
+ track("legacy");
120
+ analytics.track("ga");
121
+ segment.track("x");
170
122
 
171
- trackFeature("purchase")
123
+ // Wrong API shape for SDK (object as first argument)
124
+ tracker.track({ event: "click" });
172
125
  ```
173
126
 
174
127
  ---
175
128
 
176
- ### `eventra check`
129
+ ## Commands
177
130
 
178
- Validates detected events against config.
131
+ ### `eventra sync`
179
132
 
180
- ```bash
181
- eventra check
182
- ```
133
+ Full project scan → updates `eventra.json`:
183
134
 
184
- Detects:
135
+ - `events` — discovered event names
136
+ - `functionWrappers` — detected wrapper functions
185
137
 
186
- * New events
187
- * Removed events
188
- * Invalid or inconsistent tracking usage
138
+ ### `eventra check`
189
139
 
190
- ---
140
+ Compares config with the current scan (events + wrappers). Exit code `1` on drift.
191
141
 
192
142
  ### `eventra check --fix`
193
143
 
194
- Automatically updates config to match detected events.
195
-
196
- ```bash
197
- eventra check --fix
198
- ```
199
-
200
- ---
144
+ Writes scan results into `eventra.json`.
201
145
 
202
146
  ### `eventra watch`
203
147
 
204
- Runs in watch mode with incremental semantic analysis.
205
-
206
- ```bash
207
- eventra watch
208
- ```
209
-
210
- Only affected files are re-analyzed after changes.
211
-
212
- ---
148
+ Incremental scan with the same rules as `sync`.
213
149
 
214
150
  ### `eventra send`
215
151
 
216
- Sends events to Eventra backend.
217
-
218
- ```bash
219
- eventra send
220
- ```
152
+ Uploads `events` from config to the Eventra API. Requires `apiKey` (and optionally `endpoint`) in `eventra.json`. Events are POSTed to `POST /api/v1/cli/events` and marked as non-billable on the backend.
221
153
 
222
154
  ---
223
155
 
@@ -228,79 +160,31 @@ eventra send
228
160
  "apiKey": "",
229
161
  "endpoint": "",
230
162
  "events": [],
231
- "wrappers": [],
232
163
  "functionWrappers": [],
233
164
  "sync": {
234
165
  "include": ["**/*.{ts,tsx,js,jsx}"],
235
- "exclude": [
236
- "node_modules",
237
- "dist",
238
- ".next",
239
- ".git"
240
- ]
166
+ "exclude": ["node_modules", "dist", ".next", ".git"]
241
167
  }
242
168
  }
243
169
  ```
244
170
 
245
171
  ---
246
172
 
247
- ## Architecture Highlights
248
-
249
- ### Semantic propagation analysis
250
-
251
- Eventra resolves event propagation through wrappers and returned values.
252
-
253
- ```ts
254
- function createEvent(name: string) {
255
- return name
256
- }
257
-
258
- function trackEvent(name: string) {
259
- track(createEvent(name))
260
- }
261
- ```
262
-
263
- ---
264
-
265
- ### Incremental analysis engine
266
-
267
- Eventra does NOT rescan the whole project after every update.
268
-
269
- Instead it:
270
-
271
- 1. Updates changed files
272
- 2. Invalidates affected caches
273
- 3. Rebuilds TypeScript incrementally
274
- 4. Traverses dependency graph
275
- 5. Re-analyzes affected files only
276
-
277
- ---
278
-
279
- ### Static extraction
280
-
281
- Eventra works entirely statically.
282
-
283
- No runtime execution.
284
- No monkey patching.
285
- No SDK instrumentation.
286
-
287
- ---
288
-
289
- ## Supported Environments
173
+ ## How it works
290
174
 
291
- Eventra works with:
175
+ 1. Load project files into an incremental TypeScript program (with SDK type shim).
176
+ 2. **Phase 1** — find `Eventra` instances from `@eventra_dev/eventra-sdk` and register function wrappers that call `.track()`.
177
+ 3. **Phase 2** — resolve static event names and wrapper propagation chains.
178
+ 4. Write results to `eventra.json`.
292
179
 
293
- * JavaScript
294
- * TypeScript
295
- * Node.js projects
296
- * Browser applications
297
- * Monorepos
180
+ No runtime execution. No monkey-patching.
298
181
 
299
182
  ---
300
183
 
301
184
  ## Requirements
302
185
 
303
- * Node.js 18+
186
+ - Node.js 18+
187
+ - TypeScript/JavaScript source using `@eventra_dev/eventra-sdk`
304
188
 
305
189
  ---
306
190
 
@@ -8,9 +8,7 @@ const typescript_1 = __importDefault(require("typescript"));
8
8
  const resolver_1 = require("../resolver/resolver");
9
9
  const evaluationContext_1 = require("../shared/evaluationContext");
10
10
  const propagationExtractor_1 = require("./propagationExtractor");
11
- function isValidEvent(value) {
12
- return (value.length > 0 && value.length < 160 && /^[a-zA-Z0-9:_./-]+$/.test(value));
13
- }
11
+ const eventValidation_1 = require("../shared/eventValidation");
14
12
  function extractEvents(index, checker, _config, evaluationCache, exportCache, resolvedCallCache, returnPropagationCache, wrapperRegistry) {
15
13
  const events = new Set();
16
14
  // direct track sinks
@@ -18,8 +16,9 @@ function extractEvents(index, checker, _config, evaluationCache, exportCache, re
18
16
  for (const argument of call.trackedArguments) {
19
17
  const resolved = (0, resolver_1.resolveNodeValue)(argument, checker, (0, evaluationContext_1.createEvaluationContext)(), new Set(), evaluationCache, resolvedCallCache, returnPropagationCache, exportCache);
20
18
  for (const value of resolved.values) {
21
- if (isValidEvent(value)) {
22
- events.add(value);
19
+ const normalized = (0, eventValidation_1.normalizeEventName)(value);
20
+ if (normalized) {
21
+ events.add(normalized);
23
22
  }
24
23
  }
25
24
  }
@@ -8,9 +8,7 @@ const typescript_1 = __importDefault(require("typescript"));
8
8
  const callResolver_1 = require("../resolver/callResolver");
9
9
  const resolver_1 = require("../resolver/resolver");
10
10
  const evaluationContext_1 = require("../shared/evaluationContext");
11
- function isValidEvent(value) {
12
- return (value.length > 0 && value.length < 160 && /^[a-zA-Z0-9:_./-]+$/.test(value));
13
- }
11
+ const eventValidation_1 = require("../shared/eventValidation");
14
12
  function resolveObjectPath(expression, path) {
15
13
  let current = expression;
16
14
  for (const segment of path) {
@@ -86,8 +84,9 @@ function extractPropagation(propagation, call, checker, evaluationCache, resolve
86
84
  }
87
85
  const resolved = (0, resolver_1.resolveNodeValue)(propagation.targetNode, checker, context, new Set(), evaluationCache, resolvedCallCache, returnPropagationCache, exportCache);
88
86
  for (const value of resolved.values) {
89
- if (isValidEvent(value)) {
90
- events.add(value);
87
+ const normalized = (0, eventValidation_1.normalizeEventName)(value);
88
+ if (normalized) {
89
+ events.add(normalized);
91
90
  }
92
91
  }
93
92
  }
@@ -23,7 +23,7 @@ function scanSource(source, checker, wrapperRegistry) {
23
23
  }
24
24
  // sink detection
25
25
  if (typescript_1.default.isCallExpression(node)) {
26
- const sink = (0, sinkDetector_1.detectTrackSink)(node);
26
+ const sink = (0, sinkDetector_1.detectTrackSink)(node, checker);
27
27
  if (sink) {
28
28
  sinks.push(sink);
29
29
  const trackedArguments = sink.trackedArguments.map(tracked => {
@@ -5,53 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.detectTrackSink = detectTrackSink;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
- function isElementTrackProperty(expr) {
9
- return (typescript_1.default.isElementAccessExpression(expr) && expr.argumentExpression && typescript_1.default.isStringLiteral(expr.argumentExpression) && expr.argumentExpression.text === "track");
10
- }
11
- function isTrackIdentifier(expr) {
12
- return (typescript_1.default.isIdentifier(expr) && expr.text === "track");
13
- }
14
- function isTrackProperty(expr) {
15
- return (typescript_1.default.isPropertyAccessExpression(expr) && expr.name.text === "track");
16
- }
17
- function isOptionalTrackProperty(expr) {
18
- return (typescript_1.default.isPropertyAccessChain(expr) && expr.name.text === "track");
19
- }
8
+ const isEventraTrackCall_1 = require("../sdk/isEventraTrackCall");
9
+ /** SDK: track(name: string, options?) — event name is always the first argument. */
20
10
  function extractTrackedArguments(call) {
21
- const result = [];
22
11
  if (call.arguments.length === 0) {
23
- return result;
12
+ return [];
24
13
  }
25
14
  const first = call.arguments[0];
26
15
  if (typescript_1.default.isObjectLiteralExpression(first)) {
27
- for (const property of first.properties) {
28
- if (!typescript_1.default.isPropertyAssignment(property) && !typescript_1.default.isShorthandPropertyAssignment(property)) {
29
- continue;
30
- }
31
- const name = property.name.getText();
32
- if (name !== "event" && name !== "name" && name !== "type") {
33
- continue;
34
- }
35
- result.push({
36
- index: 0,
37
- propertyPath: [name],
38
- });
39
- }
40
- return result;
16
+ return [];
41
17
  }
42
- result.push({
43
- index: 0,
44
- propertyPath: [],
45
- });
46
- return result;
18
+ return [
19
+ {
20
+ index: 0,
21
+ propertyPath: [],
22
+ },
23
+ ];
47
24
  }
48
- function detectTrackSink(call) {
49
- const expression = call.expression;
50
- if (!isTrackIdentifier(expression) && !isTrackProperty(expression) && !isOptionalTrackProperty(expression) && !isElementTrackProperty(expression)) {
25
+ function detectTrackSink(call, checker) {
26
+ if (!(0, isEventraTrackCall_1.isEventraSdkTrackCall)(call, checker)) {
27
+ return null;
28
+ }
29
+ const trackedArguments = extractTrackedArguments(call);
30
+ if (trackedArguments.length === 0) {
51
31
  return null;
52
32
  }
53
33
  return {
54
34
  call,
55
- trackedArguments: extractTrackedArguments(call),
35
+ trackedArguments,
56
36
  };
57
37
  }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVENTRA_SDK_SHIM = exports.MAX_EVENT_NAME_LENGTH = exports.EVENTRA_SDK_PACKAGE = void 0;
4
+ exports.isEventraSdkModuleSpecifier = isEventraSdkModuleSpecifier;
5
+ exports.normalizeEventName = normalizeEventName;
6
+ exports.isValidEventName = isValidEventName;
7
+ /** Mirrors @eventra_dev/eventra-sdk runtime limits (see packages/sdk/src/client.ts). */
8
+ exports.EVENTRA_SDK_PACKAGE = "@eventra_dev/eventra-sdk";
9
+ exports.MAX_EVENT_NAME_LENGTH = 64;
10
+ function isEventraSdkModuleSpecifier(specifier) {
11
+ const normalized = specifier.replace(/\\/g, "/");
12
+ return (normalized === exports.EVENTRA_SDK_PACKAGE ||
13
+ normalized.startsWith(`${exports.EVENTRA_SDK_PACKAGE}/`));
14
+ }
15
+ function normalizeEventName(value) {
16
+ const trimmed = value.trim();
17
+ if (!trimmed || trimmed.length > exports.MAX_EVENT_NAME_LENGTH) {
18
+ return null;
19
+ }
20
+ if (!/^[a-zA-Z0-9:_./-]+$/.test(trimmed)) {
21
+ return null;
22
+ }
23
+ return trimmed;
24
+ }
25
+ function isValidEventName(value) {
26
+ return normalizeEventName(value) !== null;
27
+ }
28
+ exports.EVENTRA_SDK_SHIM = `declare module "@eventra_dev/eventra-sdk" {
29
+ export interface TrackOptions {
30
+ userId?: string;
31
+ properties?: Record<string, unknown>;
32
+ }
33
+ export interface TrackerOptions {
34
+ apiKey: string;
35
+ endpoint?: string;
36
+ }
37
+ export class Eventra {
38
+ constructor(options: TrackerOptions);
39
+ track(name: string, options?: TrackOptions): void;
40
+ }
41
+ }
42
+ `;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isEventraSdkTrackCall = isEventraSdkTrackCall;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ const eventraSdk_1 = require("./eventraSdk");
9
+ function isTrackCallee(expr) {
10
+ if (typescript_1.default.isPropertyAccessExpression(expr) || typescript_1.default.isPropertyAccessChain(expr)) {
11
+ return expr.name.text === "track";
12
+ }
13
+ if (typescript_1.default.isElementAccessExpression(expr) &&
14
+ expr.argumentExpression &&
15
+ typescript_1.default.isStringLiteral(expr.argumentExpression) &&
16
+ expr.argumentExpression.text === "track") {
17
+ return true;
18
+ }
19
+ return false;
20
+ }
21
+ function fileImportsEventraSdk(sourceFile) {
22
+ for (const stmt of sourceFile.statements) {
23
+ if (!typescript_1.default.isImportDeclaration(stmt) || !stmt.moduleSpecifier) {
24
+ continue;
25
+ }
26
+ if (!typescript_1.default.isStringLiteral(stmt.moduleSpecifier)) {
27
+ continue;
28
+ }
29
+ if ((0, eventraSdk_1.isEventraSdkModuleSpecifier)(stmt.moduleSpecifier.text)) {
30
+ return true;
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+ function getImportDeclarationForSpecifier(decl) {
36
+ let current = decl;
37
+ while (current) {
38
+ if (typescript_1.default.isImportDeclaration(current)) {
39
+ return current;
40
+ }
41
+ current = current.parent;
42
+ }
43
+ return null;
44
+ }
45
+ function isEventraImportDeclaration(decl) {
46
+ if (typescript_1.default.isImportSpecifier(decl) && decl.name.text === "Eventra") {
47
+ const importDecl = getImportDeclarationForSpecifier(decl);
48
+ if (importDecl?.moduleSpecifier && typescript_1.default.isStringLiteral(importDecl.moduleSpecifier)) {
49
+ return (0, eventraSdk_1.isEventraSdkModuleSpecifier)(importDecl.moduleSpecifier.text);
50
+ }
51
+ }
52
+ if (typescript_1.default.isImportClause(decl)) {
53
+ const parent = decl.parent;
54
+ if (parent && typescript_1.default.isImportDeclaration(parent) && parent.moduleSpecifier && typescript_1.default.isStringLiteral(parent.moduleSpecifier)) {
55
+ return (0, eventraSdk_1.isEventraSdkModuleSpecifier)(parent.moduleSpecifier.text);
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ function isEventraClassDeclaration(decl) {
61
+ if (!typescript_1.default.isClassDeclaration(decl) || decl.name?.text !== "Eventra") {
62
+ return false;
63
+ }
64
+ const file = decl.getSourceFile().fileName.replace(/\\/g, "/");
65
+ return file.includes("eventra-sdk") || file.includes("@eventra_dev");
66
+ }
67
+ function isEventraSymbol(symbol, checker, depth = 0) {
68
+ if (depth > 10) {
69
+ return false;
70
+ }
71
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
72
+ return isEventraSymbol(checker.getAliasedSymbol(symbol), checker, depth + 1);
73
+ }
74
+ for (const decl of symbol.getDeclarations() ?? []) {
75
+ if (isEventraImportDeclaration(decl) || isEventraClassDeclaration(decl)) {
76
+ return true;
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+ function isEventraNewExpression(node, checker) {
82
+ const expr = node.expression;
83
+ if (!typescript_1.default.isIdentifier(expr)) {
84
+ return false;
85
+ }
86
+ const symbol = checker.getSymbolAtLocation(expr);
87
+ return symbol ? isEventraSymbol(symbol, checker) : false;
88
+ }
89
+ function typeLooksLikeEventra(type, checker, depth = 0) {
90
+ if (depth > 8) {
91
+ return false;
92
+ }
93
+ const symbol = type.getSymbol();
94
+ if (symbol && isEventraSymbol(symbol, checker)) {
95
+ return true;
96
+ }
97
+ const typeStr = checker.typeToString(type);
98
+ if (typeStr === "Eventra" || /^Eventra<.*>$/.test(typeStr)) {
99
+ return true;
100
+ }
101
+ if (type.isUnionOrIntersection()) {
102
+ return type.types.some((t) => typeLooksLikeEventra(t, checker, depth + 1));
103
+ }
104
+ return false;
105
+ }
106
+ function isEventraReceiverExpression(receiver, checker) {
107
+ if (typescript_1.default.isNewExpression(receiver)) {
108
+ return isEventraNewExpression(receiver, checker);
109
+ }
110
+ if (typescript_1.default.isCallExpression(receiver) && typescript_1.default.isNewExpression(receiver.expression)) {
111
+ return isEventraNewExpression(receiver.expression, checker);
112
+ }
113
+ const type = checker.getTypeAtLocation(receiver);
114
+ if (typeLooksLikeEventra(type, checker)) {
115
+ return true;
116
+ }
117
+ if (typescript_1.default.isIdentifier(receiver)) {
118
+ const symbol = checker.getSymbolAtLocation(receiver);
119
+ if (!symbol) {
120
+ return false;
121
+ }
122
+ for (const decl of symbol.getDeclarations() ?? []) {
123
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
124
+ if (typescript_1.default.isNewExpression(decl.initializer) && isEventraNewExpression(decl.initializer, checker)) {
125
+ return true;
126
+ }
127
+ if (typescript_1.default.isCallExpression(decl.initializer) &&
128
+ typescript_1.default.isNewExpression(decl.initializer.expression) &&
129
+ isEventraNewExpression(decl.initializer.expression, checker)) {
130
+ return true;
131
+ }
132
+ }
133
+ if (typescript_1.default.isParameter(decl) && decl.type) {
134
+ const paramType = checker.getTypeFromTypeNode(decl.type);
135
+ if (typeLooksLikeEventra(paramType, checker)) {
136
+ return true;
137
+ }
138
+ }
139
+ if (typescript_1.default.isPropertyDeclaration(decl) && decl.type) {
140
+ const propType = checker.getTypeFromTypeNode(decl.type);
141
+ if (typeLooksLikeEventra(propType, checker)) {
142
+ return true;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ if (typescript_1.default.isPropertyAccessExpression(receiver) || typescript_1.default.isPropertyAccessChain(receiver)) {
148
+ return isEventraReceiverExpression(receiver.expression, checker);
149
+ }
150
+ return false;
151
+ }
152
+ /** Only `instance.track(...)` where instance is Eventra from @eventra_dev/eventra-sdk. */
153
+ function isEventraSdkTrackCall(call, checker) {
154
+ const expr = call.expression;
155
+ if (!isTrackCallee(expr)) {
156
+ return false;
157
+ }
158
+ const sourceFile = call.getSourceFile();
159
+ if (!fileImportsEventraSdk(sourceFile)) {
160
+ return false;
161
+ }
162
+ const receiver = typescript_1.default.isPropertyAccessExpression(expr) ||
163
+ typescript_1.default.isPropertyAccessChain(expr) ||
164
+ typescript_1.default.isElementAccessExpression(expr)
165
+ ? expr.expression
166
+ : undefined;
167
+ if (!receiver) {
168
+ return false;
169
+ }
170
+ return isEventraReceiverExpression(receiver, checker);
171
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_EVENT_NAME_LENGTH = exports.normalizeEventName = exports.isValidEventName = void 0;
4
+ var eventraSdk_1 = require("../sdk/eventraSdk");
5
+ Object.defineProperty(exports, "isValidEventName", { enumerable: true, get: function () { return eventraSdk_1.isValidEventName; } });
6
+ Object.defineProperty(exports, "normalizeEventName", { enumerable: true, get: function () { return eventraSdk_1.normalizeEventName; } });
7
+ Object.defineProperty(exports, "MAX_EVENT_NAME_LENGTH", { enumerable: true, get: function () { return eventraSdk_1.MAX_EVENT_NAME_LENGTH; } });
package/dist/cli/init.js CHANGED
@@ -23,12 +23,10 @@ async function init() {
23
23
  },
24
24
  ]);
25
25
  console.log("");
26
- console.log(chalk_1.default.gray("Eventra automatically detects:"));
27
- console.log(chalk_1.default.gray("• track('event')"));
28
- console.log(chalk_1.default.gray("• analytics.track('event')"));
29
- console.log(chalk_1.default.gray("• semantic parameter propagation"));
30
- console.log(chalk_1.default.gray("• cross-file wrappers"));
31
- console.log(chalk_1.default.gray("• parameter propagation"));
26
+ console.log(chalk_1.default.gray("Eventra detects @eventra_dev/eventra-sdk usage:"));
27
+ console.log(chalk_1.default.gray('• tracker.track("event.name")'));
28
+ console.log(chalk_1.default.gray("• function wrappers → Eventra.track()"));
29
+ console.log(chalk_1.default.gray("• cross-file propagation chains"));
32
30
  console.log("");
33
31
  const config = {
34
32
  apiKey,
package/dist/cli/watch.js CHANGED
@@ -10,8 +10,10 @@ const promises_1 = __importDefault(require("fs/promises"));
10
10
  const fast_glob_1 = __importDefault(require("fast-glob"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const config_1 = require("../config/config");
13
+ const eventraSdk_1 = require("../analysis/sdk/eventraSdk");
13
14
  const EventraEngine_1 = require("../core/EventraEngine");
14
15
  const scanResults_1 = require("../core/scanResults");
16
+ const SDK_TYPES_FILE = "__eventra_sdk_types__.d.ts";
15
17
  const processFile_1 = require("../filesystem/processFile");
16
18
  const getVirtualFile_1 = require("../filesystem/getVirtualFile");
17
19
  async function watch() {
@@ -34,6 +36,7 @@ async function watch() {
34
36
  absolute: true,
35
37
  });
36
38
  engine.beginPreload();
39
+ await engine.preloadFile(SDK_TYPES_FILE, eventraSdk_1.EVENTRA_SDK_SHIM);
37
40
  for (const file of files) {
38
41
  try {
39
42
  const abs = normalize(file);
@@ -42,14 +45,16 @@ async function watch() {
42
45
  cache.set(abs, parsed);
43
46
  trackedFiles.add(abs);
44
47
  await engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content);
45
- fileDeps.set(abs, new Set(parsed.dependencies.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep)))));
48
+ fileDeps.set(abs, new Set(parsed.dependencies
49
+ .filter((dep) => dep.startsWith(".") || dep.startsWith("/"))
50
+ .map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep)))));
46
51
  }
47
52
  catch {
48
53
  // ignore invalid files
49
54
  }
50
55
  }
51
56
  engine.endPreload();
52
- await engine.runFullAnalysis([...trackedFiles].map((f) => (0, getVirtualFile_1.getVirtualFile)(f)), config);
57
+ await engine.runFullAnalysis([SDK_TYPES_FILE, ...trackedFiles].map((f) => (0, getVirtualFile_1.getVirtualFile)(f)), config);
53
58
  await (0, scanResults_1.persistScanResults)(config, engine);
54
59
  const initialEvents = engine.getAllEvents();
55
60
  const initialWrappers = engine.getAllFunctionWrappers();
@@ -69,7 +74,9 @@ async function watch() {
69
74
  await engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
70
75
  const abs = normalize(file);
71
76
  const prevDeps = fileDeps.get(abs) ?? new Set();
72
- const nextDeps = new Set(parsed.dependencies.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep))));
77
+ const nextDeps = new Set(parsed.dependencies
78
+ .filter((dep) => dep.startsWith(".") || dep.startsWith("/"))
79
+ .map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep))));
73
80
  for (const dep of nextDeps) {
74
81
  if (prevDeps.has(dep))
75
82
  continue;
@@ -7,9 +7,11 @@ exports.scanProject = scanProject;
7
7
  const fast_glob_1 = __importDefault(require("fast-glob"));
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const eventraSdk_1 = require("../analysis/sdk/eventraSdk");
10
11
  const EventraEngine_1 = require("./EventraEngine");
11
12
  const processFile_1 = require("../filesystem/processFile");
12
13
  const getVirtualFile_1 = require("../filesystem/getVirtualFile");
14
+ const SDK_TYPES_FILE = "__eventra_sdk_types__.d.ts";
13
15
  async function getParsedFile(file, cache) {
14
16
  const existing = cache.get(file);
15
17
  if (existing)
@@ -36,15 +38,18 @@ async function scanProject(config) {
36
38
  cache.set(file, parsed);
37
39
  toScan.add(file);
38
40
  for (const dep of parsed.dependencies) {
39
- toScan.add(path_1.default.resolve(path_1.default.dirname(file), dep));
41
+ if (dep.startsWith(".") || dep.startsWith("/")) {
42
+ toScan.add(path_1.default.resolve(path_1.default.dirname(file), dep));
43
+ }
40
44
  }
41
45
  }
42
46
  catch (err) {
43
47
  console.error(`skip: ${file}`, err instanceof Error ? err.message : err);
44
48
  }
45
49
  }
46
- const virtualFiles = [...toScan].map((f) => (0, getVirtualFile_1.getVirtualFile)(f));
50
+ const virtualFiles = [SDK_TYPES_FILE, ...toScan].map((f) => (0, getVirtualFile_1.getVirtualFile)(f));
47
51
  engine.beginPreload();
52
+ await engine.preloadFile(SDK_TYPES_FILE, eventraSdk_1.EVENTRA_SDK_SHIM);
48
53
  for (const file of toScan) {
49
54
  try {
50
55
  const parsed = await getParsedFile(file, cache);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "Static analytics event extraction CLI powered by the TypeScript compiler API",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",