@eventra_dev/eventra-cli 0.1.32 → 0.1.34

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.
@@ -0,0 +1,88 @@
1
+ # Eventra CLI Architecture
2
+
3
+ ## High-Level Flow
4
+
5
+ FILES → parseUniversal → TSService → scanSource → Engine → CLI
6
+
7
+ ---
8
+
9
+ ## Diagram
10
+
11
+ ```
12
+ +-------------------+
13
+ | Source Files |
14
+ +---------+---------+
15
+ |
16
+ v
17
+ +-------------------+
18
+ | parseUniversal |
19
+ | (normalize input) |
20
+ +---------+---------+
21
+ |
22
+ v
23
+ +-------------------+
24
+ | TSService |
25
+ | (AST + Types) |
26
+ +---------+---------+
27
+ |
28
+ v
29
+ +-------------------+
30
+ | scanSource |
31
+ | (core analyzer) |
32
+ +----+------+-------+
33
+ | |
34
+ | |
35
+ v v
36
+ +----------------+----------------+
37
+ | |
38
+ +-------------------+ +-----------------------+
39
+ | Function Wrappers | | Component Wrappers |
40
+ +-------------------+ +-----------------------+
41
+ | |
42
+ +---------------+-----------------+
43
+ |
44
+ v
45
+ +-------------------+
46
+ | resolveNodeValue |
47
+ | (extract values) |
48
+ +---------+---------+
49
+ |
50
+ v
51
+ +-------------------+
52
+ | EventraEngine |
53
+ | (orchestration) |
54
+ +---------+---------+
55
+ |
56
+ v
57
+ +-------------------+
58
+ | CLI (sync/check) |
59
+ +-------------------+
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Modules
65
+
66
+ ### Parser
67
+ Extracts JS from any framework (Vue, Svelte, Astro, HTML).
68
+
69
+ ### TSService
70
+ Provides TypeScript AST and type resolution.
71
+
72
+ ### Scanner
73
+ Finds:
74
+ - track() calls
75
+ - wrappers
76
+ - values
77
+
78
+ ### Engine
79
+ Handles:
80
+ - caching
81
+ - dependency graph
82
+ - incremental updates
83
+
84
+ ---
85
+
86
+ ## Goal
87
+
88
+ Static analytics extraction without runtime.
@@ -5,153 +5,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveFunctionFromCall = resolveFunctionFromCall;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
- // HELPERS
9
- function isFunctionLike(node) {
8
+ function isFn(node) {
10
9
  return (typescript_1.default.isFunctionDeclaration(node) ||
11
10
  typescript_1.default.isMethodDeclaration(node) ||
12
- typescript_1.default.isConstructorDeclaration(node) ||
13
11
  typescript_1.default.isFunctionExpression(node) ||
14
12
  typescript_1.default.isArrowFunction(node));
15
13
  }
16
- // symbol → function
17
- function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
18
- if (!symbol)
14
+ function resolveFromSymbol(symbol, checker, seen = new Set()) {
15
+ if (seen.has(symbol))
19
16
  return null;
20
- if (visited.has(symbol))
21
- return null;
22
- if (depth > 10)
23
- return null;
24
- visited.add(symbol);
25
- // Alias unwrap (import/export)
17
+ seen.add(symbol);
26
18
  if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
27
- try {
28
- const aliased = checker.getAliasedSymbol(symbol);
29
- return resolveFromSymbol(aliased, checker, visited, depth + 1);
30
- }
31
- catch { }
32
- }
33
- // Declarations
34
- const decls = symbol.getDeclarations() ?? [];
35
- for (const decl of decls) {
36
- /**
37
- * direct function
38
- */
39
- if (isFunctionLike(decl)) {
40
- return decl;
41
- }
42
- // VARIABLE → FUNCTION const fn = () => {}
43
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
44
- if (isFunctionLike(decl.initializer)) {
45
- return decl.initializer;
46
- }
47
- const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
48
- if (innerSymbol && innerSymbol !== symbol) {
49
- const resolved = resolveFromSymbol(innerSymbol, checker, visited, depth + 1);
50
- if (resolved)
51
- return resolved;
19
+ return resolveFromSymbol(checker.getAliasedSymbol(symbol), checker, seen);
20
+ }
21
+ for (const d of symbol.getDeclarations() ?? []) {
22
+ if (isFn(d))
23
+ return d;
24
+ if (typescript_1.default.isVariableDeclaration(d) && d.initializer) {
25
+ if (isFn(d.initializer))
26
+ return d.initializer;
27
+ const s = checker.getSymbolAtLocation(d.initializer);
28
+ if (s) {
29
+ const r = resolveFromSymbol(s, checker, seen);
30
+ if (r)
31
+ return r;
52
32
  }
53
33
  }
54
- // CLASS METHOD
55
- if (typescript_1.default.isMethodDeclaration(decl)) {
56
- return decl;
57
- }
58
- // interface / type method
59
- if (typescript_1.default.isMethodSignature(decl)) {
60
- const parent = decl.parent;
61
- if (typescript_1.default.isInterfaceDeclaration(parent) ||
62
- typescript_1.default.isTypeLiteralNode(parent)) {
63
- const type = checker.getTypeAtLocation(parent);
64
- const props = type.getProperties();
65
- for (const prop of props) {
66
- const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
67
- if (resolved)
68
- return resolved;
69
- }
70
- }
71
- }
72
- // fallback recursion
73
- const inner = checker.getSymbolAtLocation(decl);
74
- if (inner && inner !== symbol) {
75
- const resolved = resolveFromSymbol(inner, checker, visited, depth + 1);
76
- if (resolved)
77
- return resolved;
78
- }
79
- }
80
- // Exports (re-export)
81
- const exports = symbol.exports;
82
- if (exports) {
83
- for (const exp of exports.values()) {
84
- const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
85
- if (resolved)
86
- return resolved;
87
- }
88
34
  }
89
35
  return null;
90
36
  }
91
- // MAIN FUNCTION
92
37
  function resolveFunctionFromCall(expr, checker) {
93
- // obj.method()
38
+ // foo.bar()
94
39
  if (typescript_1.default.isPropertyAccessExpression(expr)) {
95
- let symbol = checker.getSymbolAtLocation(expr.name);
96
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
97
- try {
98
- symbol = checker.getAliasedSymbol(symbol);
99
- }
100
- catch { }
101
- }
102
- if (symbol) {
103
- const resolved = resolveFromSymbol(symbol, checker);
104
- if (resolved)
105
- return resolved;
106
- }
107
- }
108
- // obj["method"]()
109
- if (typescript_1.default.isElementAccessExpression(expr)) {
110
- const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
111
- if (symbol) {
112
- const resolved = resolveFromSymbol(symbol, checker);
113
- if (resolved)
114
- return resolved;
40
+ const s = checker.getSymbolAtLocation(expr.name);
41
+ if (s) {
42
+ const r = resolveFromSymbol(s, checker);
43
+ if (r)
44
+ return r;
115
45
  }
116
46
  }
117
- // Call signatures
47
+ // direct call
118
48
  const type = checker.getTypeAtLocation(expr);
119
- const signatures = type.getCallSignatures();
120
- for (const sig of signatures) {
121
- const decl = sig.getDeclaration();
122
- if (!decl)
123
- continue;
124
- if (isFunctionLike(decl)) {
125
- return decl;
126
- }
127
- if (typescript_1.default.isMethodSignature(decl)) {
128
- const symbol = checker.getSymbolAtLocation(decl.name);
129
- if (symbol) {
130
- const resolved = resolveFromSymbol(symbol, checker);
131
- if (resolved)
132
- return resolved;
133
- }
134
- }
135
- }
136
- // Identifier fallback
137
- if (typescript_1.default.isIdentifier(expr)) {
138
- let symbol = checker.getSymbolAtLocation(expr);
139
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
140
- try {
141
- symbol = checker.getAliasedSymbol(symbol);
142
- }
143
- catch { }
144
- }
145
- if (symbol) {
146
- const resolved = resolveFromSymbol(symbol, checker);
147
- if (resolved)
148
- return resolved;
149
- }
150
- }
151
- // LAST fallback
152
- const symbol = checker.getSymbolAtLocation(expr);
153
- if (symbol) {
154
- return resolveFromSymbol(symbol, checker);
155
- }
49
+ for (const sig of type.getCallSignatures()) {
50
+ const d = sig.getDeclaration();
51
+ if (d && isFn(d))
52
+ return d;
53
+ }
54
+ const s = checker.getSymbolAtLocation(expr);
55
+ if (s)
56
+ return resolveFromSymbol(s, checker);
156
57
  return null;
157
58
  }
@@ -15,9 +15,6 @@ class TSService {
15
15
  compilerOptions;
16
16
  baseUrl;
17
17
  paths;
18
- usage = new Map();
19
- tick = 0;
20
- MAX_FILES = 2000;
21
18
  constructor(root) {
22
19
  this.root = root;
23
20
  const configPath = typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists, "tsconfig.json");
@@ -49,11 +46,12 @@ class TSService {
49
46
  this.compilerOptions = compilerOptions;
50
47
  this.baseUrl = baseUrl;
51
48
  this.paths = paths;
49
+ const allFiles = typescript_1.default.sys.readDirectory(root, [".ts", ".tsx", ".js", ".jsx"], undefined, undefined);
50
+ for (const f of allFiles) {
51
+ this.fileNames.add(this.normalize(f));
52
+ }
52
53
  const host = {
53
54
  getScriptFileNames: () => {
54
- if (this.fileNames.size === 0) {
55
- return typescript_1.default.sys.readDirectory(this.root, [".ts", ".tsx", ".js", ".jsx"], undefined, undefined);
56
- }
57
55
  return Array.from(this.fileNames);
58
56
  },
59
57
  getScriptVersion: (file) => {
@@ -62,11 +60,12 @@ class TSService {
62
60
  },
63
61
  getScriptSnapshot: (file) => {
64
62
  file = this.normalize(file);
63
+ // из памяти
65
64
  const f = this.files.get(file);
66
65
  if (f) {
67
- this.touch(file);
68
66
  return typescript_1.default.ScriptSnapshot.fromString(f.content);
69
67
  }
68
+ // virtual TSX (vue/svelte/astro)
70
69
  if (file.endsWith(".tsx")) {
71
70
  const original = file.replace(/\.tsx$/, "");
72
71
  if (fs_1.default.existsSync(original)) {
@@ -76,8 +75,6 @@ class TSService {
76
75
  version: 1,
77
76
  });
78
77
  this.fileNames.add(file);
79
- this.touch(file);
80
- this.evictIfNeeded();
81
78
  return typescript_1.default.ScriptSnapshot.fromString(content);
82
79
  }
83
80
  }
@@ -89,8 +86,6 @@ class TSService {
89
86
  version: 1,
90
87
  });
91
88
  this.fileNames.add(file);
92
- this.touch(file);
93
- this.evictIfNeeded();
94
89
  return typescript_1.default.ScriptSnapshot.fromString(content);
95
90
  }
96
91
  }
@@ -112,35 +107,12 @@ class TSService {
112
107
  };
113
108
  this.service = typescript_1.default.createLanguageService(host);
114
109
  }
115
- // INTERNAL
116
110
  normalize(file) {
117
111
  return path_1.default.resolve(file).replace(/\\/g, "/");
118
112
  }
119
- touch(file) {
120
- this.tick++;
121
- this.usage.set(file, this.tick);
122
- }
123
- evictIfNeeded() {
124
- if (this.fileNames.size <= this.MAX_FILES)
125
- return;
126
- let oldest = null;
127
- let oldestTick = Infinity;
128
- for (const f of this.fileNames) {
129
- const t = this.usage.get(f) ?? 0;
130
- if (t < oldestTick) {
131
- oldestTick = t;
132
- oldest = f;
133
- }
134
- }
135
- if (oldest) {
136
- this.files.delete(oldest);
137
- this.fileNames.delete(oldest);
138
- this.usage.delete(oldest);
139
- }
140
- }
141
113
  ensureProgram() {
142
114
  try {
143
- this.service.getProgram()?.getTypeChecker();
115
+ this.service.getProgram();
144
116
  }
145
117
  catch { }
146
118
  }
@@ -153,15 +125,12 @@ class TSService {
153
125
  version: prev ? prev.version + 1 : 1,
154
126
  });
155
127
  this.fileNames.add(file);
156
- this.touch(file);
157
- this.evictIfNeeded();
158
128
  this.ensureProgram();
159
129
  }
160
130
  removeFile(file) {
161
131
  file = this.normalize(file);
162
132
  this.files.delete(file);
163
133
  this.fileNames.delete(file);
164
- this.usage.delete(file);
165
134
  this.ensureProgram();
166
135
  }
167
136
  getProgram() {
@@ -6,32 +6,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveNodeValue = resolveNodeValue;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
8
  const exportResolver_1 = require("./exportResolver");
9
+ const callResolver_1 = require("./callResolver");
9
10
  function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
10
- if (seen.has(node)) {
11
+ if (seen.has(node))
11
12
  return { values: [], dynamic: true };
12
- }
13
13
  seen.add(node);
14
- // PARAM MAP (wrapper args)
14
+ // param passthrough
15
15
  if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
16
16
  return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
17
17
  }
18
- // STRING
18
+ // string
19
19
  if (typescript_1.default.isStringLiteral(node)) {
20
20
  return { values: [node.text], dynamic: false };
21
21
  }
22
- // TEMPLATE
22
+ // template
23
23
  if (typescript_1.default.isTemplateExpression(node)) {
24
24
  let results = node.head.text ? [node.head.text] : [""];
25
25
  for (const span of node.templateSpans) {
26
- const res = resolveNodeValue(span.expression, checker, paramMap, seen);
27
- results = results.flatMap(r => res.values.length
28
- ? res.values.map(v => r + v)
29
- : [r + "*"]);
30
- results = results.map(r => r + span.literal.text);
26
+ const r = resolveNodeValue(span.expression, checker, paramMap, seen);
27
+ results = results.flatMap(s => r.values.length ? r.values.map(v => s + v) : [s + "*"]);
28
+ results = results.map(s => s + span.literal.text);
31
29
  }
32
30
  return { values: results, dynamic: true };
33
31
  }
34
- // CONDITIONAL (a ? b : c)
32
+ // ternary
35
33
  if (typescript_1.default.isConditionalExpression(node)) {
36
34
  const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
37
35
  const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
@@ -40,99 +38,112 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
40
38
  dynamic: true,
41
39
  };
42
40
  }
43
- // OBJECT → берём ВСЕ значения (без привязки к ключам)
41
+ // object (event/name priority)
44
42
  if (typescript_1.default.isObjectLiteralExpression(node)) {
45
- const values = [];
46
- for (const prop of node.properties) {
47
- if (!typescript_1.default.isPropertyAssignment(prop))
43
+ for (const p of node.properties) {
44
+ if (typescript_1.default.isPropertyAssignment(p)) {
45
+ const key = p.name.getText();
46
+ if (key === "event" || key === "name") {
47
+ return resolveNodeValue(p.initializer, checker, paramMap, seen);
48
+ }
49
+ }
50
+ }
51
+ const vals = [];
52
+ for (const p of node.properties) {
53
+ if (!typescript_1.default.isPropertyAssignment(p))
48
54
  continue;
49
- const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
50
- values.push(...res.values);
55
+ const r = resolveNodeValue(p.initializer, checker, paramMap, seen);
56
+ vals.push(...r.values);
51
57
  }
52
- return {
53
- values: [...new Set(values)],
54
- dynamic: true,
55
- };
58
+ return { values: [...new Set(vals)], dynamic: true };
56
59
  }
57
- // ARRAY
60
+ // array
58
61
  if (typescript_1.default.isArrayLiteralExpression(node)) {
59
- const values = [];
62
+ const vals = [];
60
63
  for (const el of node.elements) {
61
- const res = resolveNodeValue(el, checker, paramMap, seen);
62
- values.push(...res.values);
64
+ const r = resolveNodeValue(el, checker, paramMap, seen);
65
+ vals.push(...r.values);
63
66
  }
67
+ return { values: [...new Set(vals)], dynamic: true };
68
+ }
69
+ // concat
70
+ if (typescript_1.default.isBinaryExpression(node) &&
71
+ node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
72
+ const l = resolveNodeValue(node.left, checker, paramMap, seen);
73
+ const r = resolveNodeValue(node.right, checker, paramMap, seen);
64
74
  return {
65
- values: [...new Set(values)],
75
+ values: l.values.flatMap(a => r.values.length ? r.values.map(b => a + b) : [a + "*"]),
66
76
  dynamic: true,
67
77
  };
68
78
  }
69
- // BINARY CONCAT
70
- if (typescript_1.default.isBinaryExpression(node)) {
71
- if (node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
72
- const left = resolveNodeValue(node.left, checker, paramMap, seen);
73
- const right = resolveNodeValue(node.right, checker, paramMap, seen);
74
- return {
75
- values: left.values.flatMap(l => right.values.length
76
- ? right.values.map(r => l + r)
77
- : [l + "*"]),
78
- dynamic: true,
79
- };
80
- }
81
- }
82
- // IDENTIFIER
79
+ // identifier (const / enum)
83
80
  if (typescript_1.default.isIdentifier(node)) {
84
- let symbol = checker.getSymbolAtLocation(node);
85
- if (!symbol)
81
+ let s = checker.getSymbolAtLocation(node);
82
+ if (!s)
86
83
  return { values: [], dynamic: true };
87
- symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
88
- // const VAR = "click"
89
- for (const decl of symbol.getDeclarations() ?? []) {
90
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
91
- return resolveNodeValue(decl.initializer, checker, paramMap, seen);
84
+ s = (0, exportResolver_1.resolveExportedSymbol)(s, checker) ?? s;
85
+ for (const d of s.getDeclarations() ?? []) {
86
+ if (typescript_1.default.isVariableDeclaration(d) && d.initializer) {
87
+ return resolveNodeValue(d.initializer, checker, paramMap, seen);
92
88
  }
93
- // enum
94
- if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
95
- if (typescript_1.default.isStringLiteral(decl.initializer)) {
96
- return {
97
- values: [decl.initializer.text],
98
- dynamic: false,
99
- };
100
- }
89
+ if (typescript_1.default.isEnumMember(d) && d.initializer && typescript_1.default.isStringLiteral(d.initializer)) {
90
+ return { values: [d.initializer.text], dynamic: false };
101
91
  }
102
92
  }
103
93
  return { values: [], dynamic: true };
104
94
  }
105
- // PROPERTY ACCESS (ENUM / CONST)
95
+ // PROPERTY ACCESS (OBJECT + ENUM)
106
96
  if (typescript_1.default.isPropertyAccessExpression(node)) {
107
- const symbol = checker.getSymbolAtLocation(node);
108
- if (symbol) {
109
- let s = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
110
- if (s.flags & typescript_1.default.SymbolFlags.Alias) {
111
- s = checker.getAliasedSymbol(s);
112
- }
113
- for (const decl of s.getDeclarations() ?? []) {
114
- if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
115
- if (typescript_1.default.isStringLiteral(decl.initializer)) {
116
- return {
117
- values: [decl.initializer.text],
118
- dynamic: false,
119
- };
97
+ const obj = node.expression;
98
+ const prop = node.name.text;
99
+ // object literal lookup
100
+ if (typescript_1.default.isIdentifier(obj)) {
101
+ const sym = checker.getSymbolAtLocation(obj);
102
+ if (sym) {
103
+ const s = (0, exportResolver_1.resolveExportedSymbol)(sym, checker) ?? sym;
104
+ for (const d of s.getDeclarations() ?? []) {
105
+ if (typescript_1.default.isVariableDeclaration(d) &&
106
+ d.initializer &&
107
+ typescript_1.default.isObjectLiteralExpression(d.initializer)) {
108
+ for (const p of d.initializer.properties) {
109
+ if (typescript_1.default.isPropertyAssignment(p) &&
110
+ p.name.getText() === prop) {
111
+ return resolveNodeValue(p.initializer, checker, paramMap, seen);
112
+ }
113
+ }
120
114
  }
121
115
  }
122
116
  }
123
117
  }
118
+ // enum fallback
119
+ const sym = checker.getSymbolAtLocation(node);
120
+ if (sym) {
121
+ let s = (0, exportResolver_1.resolveExportedSymbol)(sym, checker) ?? sym;
122
+ if (s.flags & typescript_1.default.SymbolFlags.Alias)
123
+ s = checker.getAliasedSymbol(s);
124
+ for (const d of s.getDeclarations() ?? []) {
125
+ if (typescript_1.default.isEnumMember(d) && d.initializer && typescript_1.default.isStringLiteral(d.initializer)) {
126
+ return { values: [d.initializer.text], dynamic: false };
127
+ }
128
+ }
129
+ }
124
130
  return { values: [], dynamic: true };
125
131
  }
126
- // CALL → unwrap args
132
+ // CALL (unwrap + dive into wrapper body)
127
133
  if (typescript_1.default.isCallExpression(node)) {
128
- const values = [];
129
- for (const arg of node.arguments) {
130
- const res = resolveNodeValue(arg, checker, paramMap, seen);
131
- values.push(...res.values);
134
+ const vals = [];
135
+ // args
136
+ for (const a of node.arguments) {
137
+ const r = resolveNodeValue(a, checker, paramMap, seen);
138
+ vals.push(...r.values);
139
+ }
140
+ // dive into called function (wrapper support)
141
+ const fn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
142
+ if (fn?.body) {
143
+ const r = resolveNodeValue(fn.body, checker, paramMap, seen);
144
+ vals.push(...r.values);
132
145
  }
133
- return values.length
134
- ? { values, dynamic: true }
135
- : { values: [], dynamic: true };
146
+ return { values: [...new Set(vals)], dynamic: true };
136
147
  }
137
148
  return { values: [], dynamic: true };
138
149
  }