@eventra_dev/eventra-cli 0.1.33 → 0.2.1

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.
@@ -7,23 +7,14 @@ exports.scanSource = scanSource;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
8
  const resolver_1 = require("./resolver");
9
9
  const callResolver_1 = require("./callResolver");
10
- const functionAnalyzer_1 = require("./functionAnalyzer");
11
10
  const exportResolver_1 = require("./exportResolver");
12
- // UTILS
13
- function getJsxAttrName(name) {
14
- if (typescript_1.default.isIdentifier(name))
15
- return name.text;
16
- if (typescript_1.default.isJsxNamespacedName(name))
17
- return name.name.text;
18
- return "";
19
- }
20
- function getJsxTagName(tag) {
21
- if (typescript_1.default.isIdentifier(tag))
22
- return tag.text;
23
- if (typescript_1.default.isPropertyAccessExpression(tag)) {
24
- return tag.name.text;
25
- }
26
- return "";
11
+ // HELPERS
12
+ function isValidEvent(v) {
13
+ return (v &&
14
+ v.length > 1 &&
15
+ v.length < 120 &&
16
+ !v.includes(" ") &&
17
+ /^[a-zA-Z0-9._:-]+$/.test(v));
27
18
  }
28
19
  function getCallName(expr) {
29
20
  if (typescript_1.default.isIdentifier(expr))
@@ -32,163 +23,132 @@ function getCallName(expr) {
32
23
  return expr.name.text;
33
24
  return "";
34
25
  }
35
- function isValidEvent(v) {
36
- return (v &&
37
- v.length > 1 &&
38
- v.length < 100 &&
39
- !v.includes(" ") &&
40
- /^[a-zA-Z0-9._:-]+$/.test(v));
26
+ function getJsxTagName(tag) {
27
+ if (typescript_1.default.isIdentifier(tag))
28
+ return tag.text;
29
+ if (typescript_1.default.isPropertyAccessExpression(tag))
30
+ return tag.name.text;
31
+ return tag.getText();
41
32
  }
42
- // COMPONENT ANALYSIS
43
- function getComponentPropsMap(fn) {
44
- const map = new Map();
45
- const firstParam = fn.parameters[0];
46
- if (!firstParam)
47
- return map;
48
- // ({ event }) => ...
49
- if (typescript_1.default.isObjectBindingPattern(firstParam.name)) {
50
- for (const el of firstParam.name.elements) {
51
- if (!typescript_1.default.isBindingElement(el))
52
- continue;
53
- const propName = el.propertyName?.getText() || el.name.getText();
54
- map.set(el.name.getText(), propName);
33
+ function getJsxAttrName(name) {
34
+ if (typescript_1.default.isIdentifier(name))
35
+ return name.text;
36
+ if (typescript_1.default.isJsxNamespacedName(name))
37
+ return name.name.text;
38
+ return "";
39
+ }
40
+ // MAIN
41
+ function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
42
+ const events = new Set();
43
+ const detectedFunctionWrappers = new Set();
44
+ const detectedComponentWrappers = new Map();
45
+ if (visited.has(source.fileName)) {
46
+ return { events, detectedFunctionWrappers, detectedComponentWrappers };
47
+ }
48
+ visited.add(source.fileName);
49
+ // TRACK DETECTION
50
+ function isTrackCall(expr) {
51
+ if (typescript_1.default.isIdentifier(expr)) {
52
+ return expr.text === "track";
55
53
  }
54
+ if (typescript_1.default.isPropertyAccessExpression(expr)) {
55
+ return expr.name.text === "track";
56
+ }
57
+ return false;
56
58
  }
57
- // (props) => props.event
58
- if (typescript_1.default.isIdentifier(firstParam.name)) {
59
- map.set(firstParam.name.text, "*");
59
+ function extractTrackCall(node, paramMap) {
60
+ if (!isTrackCall(node.expression))
61
+ return;
62
+ const arg = node.arguments[0];
63
+ if (!arg)
64
+ return;
65
+ const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
66
+ for (const v of res.values) {
67
+ if (isValidEvent(v)) {
68
+ events.add(v);
69
+ }
70
+ }
60
71
  }
61
- return map;
62
- }
63
- function findTrackedPropName(fn, checker) {
64
- const propMap = getComponentPropsMap(fn);
65
- let result = null;
66
- function resolveIdentifierToProp(node) {
67
- const mapped = propMap.get(node.text);
68
- if (mapped && mapped !== "*")
69
- return mapped;
70
- const symbol = checker.getSymbolAtLocation(node);
71
- if (!symbol)
72
+ // WRAPPER DETECTION
73
+ function isWrapperFunction(fn) {
74
+ let found = false;
75
+ function walk(n) {
76
+ if (found)
77
+ return;
78
+ if (typescript_1.default.isCallExpression(n) && isTrackCall(n.expression)) {
79
+ found = true;
80
+ return;
81
+ }
82
+ typescript_1.default.forEachChild(n, walk);
83
+ }
84
+ if (fn.body)
85
+ walk(fn.body);
86
+ return found;
87
+ }
88
+ // COMPONENT WRAPPER
89
+ function detectComponentWrapper(tag) {
90
+ const tagName = getJsxTagName(tag);
91
+ if (wrapperCache.has(tagName)) {
92
+ return wrapperCache.get(tagName);
93
+ }
94
+ let symbol = checker.getSymbolAtLocation(tag);
95
+ if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
96
+ symbol = checker.getAliasedSymbol(symbol);
97
+ }
98
+ if (!symbol) {
99
+ wrapperCache.set(tagName, null);
72
100
  return null;
101
+ }
102
+ symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
73
103
  for (const decl of symbol.getDeclarations() ?? []) {
74
- // const e = props.event
104
+ let fn = null;
105
+ if (typescript_1.default.isFunctionDeclaration(decl))
106
+ fn = decl;
75
107
  if (typescript_1.default.isVariableDeclaration(decl) &&
76
108
  decl.initializer &&
77
- typescript_1.default.isPropertyAccessExpression(decl.initializer)) {
78
- return decl.initializer.name.text;
109
+ (typescript_1.default.isArrowFunction(decl.initializer) ||
110
+ typescript_1.default.isFunctionExpression(decl.initializer))) {
111
+ fn = decl.initializer;
79
112
  }
80
- // const e = event
81
- if (typescript_1.default.isVariableDeclaration(decl) &&
82
- decl.initializer &&
83
- typescript_1.default.isIdentifier(decl.initializer)) {
84
- const inner = propMap.get(decl.initializer.text);
85
- if (inner)
86
- return inner;
87
- }
88
- }
89
- return null;
90
- }
91
- function visit(node) {
92
- if (result)
93
- return;
94
- if (typescript_1.default.isCallExpression(node)) {
95
- const expr = node.expression;
96
- const isTrack = (typescript_1.default.isIdentifier(expr) && expr.text === "track") ||
97
- (typescript_1.default.isPropertyAccessExpression(expr) &&
98
- expr.name.text === "track");
99
- if (!isTrack) {
100
- typescript_1.default.forEachChild(node, visit);
101
- return;
102
- }
103
- for (const arg of node.arguments) {
104
- // track(event)
105
- if (typescript_1.default.isIdentifier(arg)) {
106
- const prop = resolveIdentifierToProp(arg);
107
- if (prop) {
108
- result = prop;
109
- return;
110
- }
111
- }
112
- // track(props.a.b.event)
113
- if (typescript_1.default.isPropertyAccessExpression(arg)) {
114
- let current = arg;
115
- while (typescript_1.default.isPropertyAccessExpression(current)) {
116
- result = current.name.text;
113
+ if (!fn || !fn.body)
114
+ continue;
115
+ let prop = null;
116
+ function walk(n) {
117
+ if (prop)
118
+ return;
119
+ if (typescript_1.default.isCallExpression(n) && isTrackCall(n.expression)) {
120
+ const arg = n.arguments[0];
121
+ if (typescript_1.default.isPropertyAccessExpression(arg)) {
122
+ prop = arg.name.text;
117
123
  return;
118
124
  }
119
125
  }
120
- // track(fn())
121
- if (typescript_1.default.isCallExpression(arg)) {
122
- const inner = (0, callResolver_1.resolveFunctionFromCall)(arg.expression, checker);
123
- if (inner?.body) {
124
- visit(inner.body);
125
- if (result)
126
- return;
127
- }
128
- }
126
+ typescript_1.default.forEachChild(n, walk);
127
+ }
128
+ walk(fn.body);
129
+ wrapperCache.set(tagName, prop);
130
+ if (prop) {
131
+ detectedComponentWrappers.set(tagName, prop);
132
+ return prop;
129
133
  }
130
134
  }
131
- typescript_1.default.forEachChild(node, visit);
132
- }
133
- if (fn.body)
134
- visit(fn.body);
135
- return result;
136
- }
137
- // MAIN SCANNER
138
- function scanSource(source, checker, config, visited = new Set(), inheritedParamMap, wrapperCache = new Map()) {
139
- const events = new Set();
140
- const detectedFunctionWrappers = new Set();
141
- const detectedComponentWrappers = new Map();
142
- if (visited.has(source.fileName) || visited.size > 200) {
143
- return { events, detectedFunctionWrappers, detectedComponentWrappers };
135
+ wrapperCache.set(tagName, null);
136
+ return null;
144
137
  }
145
- visited.add(source.fileName);
138
+ // VISITOR
146
139
  function visit(node) {
147
- // JSX WRAPPERS
140
+ // JSX
148
141
  if (typescript_1.default.isJsxSelfClosingElement(node) || typescript_1.default.isJsxOpeningElement(node)) {
149
142
  const tagName = getJsxTagName(node.tagName);
150
- // skip html
151
143
  if (tagName[0] === tagName[0].toLowerCase()) {
152
144
  typescript_1.default.forEachChild(node, visit);
153
145
  return;
154
146
  }
155
147
  let wrapper = config.wrappers.find(w => w.name === tagName);
156
- // AUTO DETECT
157
148
  if (!wrapper) {
158
- if (wrapperCache.has(tagName)) {
159
- const cached = wrapperCache.get(tagName);
160
- if (cached)
161
- wrapper = { name: tagName, prop: cached };
162
- }
163
- else {
164
- let symbol = checker.getSymbolAtLocation(node.tagName);
165
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
166
- symbol = checker.getAliasedSymbol(symbol);
167
- }
168
- if (symbol) {
169
- symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
170
- for (const decl of symbol.getDeclarations() ?? []) {
171
- let fn = null;
172
- if (typescript_1.default.isFunctionDeclaration(decl))
173
- fn = decl;
174
- if (typescript_1.default.isVariableDeclaration(decl) &&
175
- decl.initializer &&
176
- (typescript_1.default.isArrowFunction(decl.initializer) ||
177
- typescript_1.default.isFunctionExpression(decl.initializer))) {
178
- fn = decl.initializer;
179
- }
180
- if (!fn)
181
- continue;
182
- const prop = findTrackedPropName(fn, checker);
183
- wrapperCache.set(tagName, prop ?? null);
184
- if (prop) {
185
- wrapper = { name: tagName, prop };
186
- detectedComponentWrappers.set(tagName, prop);
187
- break;
188
- }
189
- }
190
- }
191
- }
149
+ const prop = detectComponentWrapper(node.tagName);
150
+ if (prop)
151
+ wrapper = { name: tagName, prop };
192
152
  }
193
153
  if (wrapper) {
194
154
  for (const attr of node.attributes.properties) {
@@ -200,15 +160,15 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
200
160
  if (!attr.initializer)
201
161
  continue;
202
162
  if (typescript_1.default.isStringLiteral(attr.initializer)) {
203
- const v = attr.initializer.text;
204
- if (isValidEvent(v))
205
- events.add(v);
163
+ if (isValidEvent(attr.initializer.text)) {
164
+ events.add(attr.initializer.text);
165
+ }
206
166
  }
207
167
  if (typescript_1.default.isJsxExpression(attr.initializer)) {
208
168
  const expr = attr.initializer.expression;
209
169
  if (!expr)
210
170
  continue;
211
- const res = (0, resolver_1.resolveNodeValue)(expr, checker, inheritedParamMap);
171
+ const res = (0, resolver_1.resolveNodeValue)(expr, checker);
212
172
  res.values.forEach(v => {
213
173
  if (isValidEvent(v))
214
174
  events.add(v);
@@ -219,65 +179,40 @@ function scanSource(source, checker, config, visited = new Set(), inheritedParam
219
179
  typescript_1.default.forEachChild(node, visit);
220
180
  return;
221
181
  }
222
- // FUNCTION CALLS
182
+ // CALL
223
183
  if (typescript_1.default.isCallExpression(node)) {
224
- let trackParamIndex = null;
225
- let isTracking = false;
226
- const callName = getCallName(node.expression);
227
- // direct .track()
228
- if (typescript_1.default.isPropertyAccessExpression(node.expression) &&
229
- node.expression.name.text === "track") {
230
- isTracking = true;
231
- trackParamIndex = 0;
232
- }
233
- // config wrapper
234
- const manualWrapper = config.functionWrappers.find(w => w.name === callName);
235
- if (manualWrapper) {
236
- isTracking = true;
237
- trackParamIndex = 0;
238
- }
239
- // resolve function → check body
240
184
  const resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
241
- if (resolvedFn) {
242
- const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
243
- if (idx !== null) {
244
- isTracking = true;
245
- trackParamIndex = idx;
246
- if (!manualWrapper && callName !== "track") {
247
- detectedFunctionWrappers.add(callName);
248
- }
249
- }
250
- }
251
- if (!isTracking) {
252
- typescript_1.default.forEachChild(node, visit);
253
- return;
254
- }
255
185
  const paramMap = new Map(inheritedParamMap ?? []);
256
- if (resolvedFn && typescript_1.default.isFunctionLike(resolvedFn)) {
257
- resolvedFn.parameters.forEach((param, i) => {
186
+ if (resolvedFn) {
187
+ resolvedFn.parameters.forEach((p, i) => {
258
188
  const arg = node.arguments[i];
259
189
  if (!arg)
260
190
  return;
261
- if (typescript_1.default.isIdentifier(param.name)) {
262
- paramMap.set(param.name.text, arg);
191
+ if (typescript_1.default.isIdentifier(p.name)) {
192
+ paramMap.set(p.name.text, arg);
263
193
  }
264
194
  });
265
195
  }
266
- if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
267
- const res = (0, resolver_1.resolveNodeValue)(node.arguments[trackParamIndex], checker, paramMap);
268
- res.values.forEach(v => {
269
- if (isValidEvent(v))
270
- events.add(v);
271
- });
196
+ extractTrackCall(node, paramMap);
197
+ if (resolvedFn && isWrapperFunction(resolvedFn)) {
198
+ const name = getCallName(node.expression);
199
+ if (name && name !== "track") {
200
+ detectedFunctionWrappers.add(name);
201
+ }
202
+ if (resolvedFn.body) {
203
+ visit(resolvedFn.body);
204
+ }
272
205
  }
273
- // CROSS FILE
274
- if (resolvedFn) {
206
+ if (resolvedFn && resolvedFn.body) {
275
207
  const sf = resolvedFn.getSourceFile();
276
208
  if (!visited.has(sf.fileName)) {
277
209
  const res = scanSource(sf, checker, config, visited, paramMap, wrapperCache);
278
210
  res.events.forEach(e => events.add(e));
279
211
  }
280
212
  }
213
+ node.arguments.forEach(visit);
214
+ visit(node.expression);
215
+ return;
281
216
  }
282
217
  typescript_1.default.forEachChild(node, visit);
283
218
  }
@@ -184,7 +184,7 @@ function parseUniversal(content, file) {
184
184
  deps.push(...dyn.deps);
185
185
  parts.push(...dyn.calls);
186
186
  return {
187
- code: [...new Set(parts)].join("\n"),
187
+ code: content,
188
188
  deps: [...new Set(deps.filter(Boolean))],
189
189
  };
190
190
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.33",
3
+ "version": "0.2.1",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -25,28 +25,20 @@ export async function check({ fix = false }: { fix?: boolean }) {
25
25
 
26
26
  const engine = new EventraEngine(process.cwd());
27
27
 
28
+ const cache = new Map<string, { content: string; deps: string[] }>();
28
29
  const toScan = new Set<string>();
29
30
 
30
- const cache = new Map<
31
- string,
32
- { content: string; deps: string[] }
33
- >();
34
-
35
- // COLLECT + CACHE
31
+ // COLLECT
36
32
  for (const file of files) {
37
33
  try {
38
34
  const raw = await fs.readFile(file, "utf-8");
39
-
40
35
  if (raw.length > 2_000_000) continue;
41
36
 
42
37
  const parsed = processFile(file, raw);
43
38
 
44
39
  cache.set(file, parsed);
45
40
  toScan.add(file);
46
-
47
- for (const dep of parsed.deps) {
48
- toScan.add(path.resolve(dep));
49
- }
41
+ parsed.deps.forEach(dep => toScan.add(path.resolve(dep)));
50
42
 
51
43
  } catch {}
52
44
  }
@@ -54,17 +46,9 @@ export async function check({ fix = false }: { fix?: boolean }) {
54
46
  // PRELOAD
55
47
  for (const file of toScan) {
56
48
  try {
57
- let parsed = cache.get(file);
58
-
59
- if (!parsed) {
60
- const raw = await fs.readFile(file, "utf-8");
61
- parsed = processFile(file, raw);
62
- cache.set(file, parsed);
63
- }
64
-
65
- const virtual = getVirtualFile(file);
66
- engine.preloadFile(virtual, parsed.content);
67
-
49
+ const parsed = cache.get(file);
50
+ if (!parsed) continue;
51
+ engine.preloadFile(getVirtualFile(file), parsed.content);
68
52
  } catch {}
69
53
  }
70
54
 
@@ -74,49 +58,34 @@ export async function check({ fix = false }: { fix?: boolean }) {
74
58
  const parsed = cache.get(file);
75
59
  if (!parsed) continue;
76
60
 
77
- const virtual = getVirtualFile(file);
78
-
79
- engine.scanFile(virtual, parsed.content, config);
61
+ engine.scanFile(
62
+ getVirtualFile(file),
63
+ parsed.content,
64
+ config
65
+ );
80
66
 
81
67
  } catch {}
82
68
  }
83
69
 
84
- // DIFF
85
70
  const found = new Set(engine.getAllEvents());
86
71
  const known = new Set(config.events ?? []);
87
72
 
88
- const newEvents = [...found].filter(e => !known.has(e));
73
+ const added = [...found].filter(e => !known.has(e));
89
74
  const removed = [...known].filter(e => !found.has(e));
90
75
 
91
- // FIX MODE
92
76
  if (fix) {
93
77
  config.events = [...found].sort();
94
78
  await saveConfig(config);
95
-
96
- console.log(
97
- chalk.green(`\nSynced (${config.events.length} events)`)
98
- );
79
+ console.log(chalk.green("Synced"));
99
80
  return;
100
81
  }
101
82
 
102
- // CHECK MODE
103
- if (newEvents.length || removed.length) {
104
- if (newEvents.length) {
105
- console.log(chalk.red("\nNew events:"));
106
- newEvents.forEach(e =>
107
- console.log(chalk.red(`+ ${e}`))
108
- );
109
- }
110
-
111
- if (removed.length) {
112
- console.log(chalk.yellow("\nRemoved events:"));
113
- removed.forEach(e =>
114
- console.log(chalk.yellow(`- ${e}`))
115
- );
116
- }
83
+ if (added.length || removed.length) {
84
+ added.forEach(e => console.log(chalk.red(`+ ${e}`)));
85
+ removed.forEach(e => console.log(chalk.yellow(`- ${e}`)));
117
86
 
118
87
  process.exit(1);
119
88
  }
120
89
 
121
- console.log(chalk.green("\nAll good"));
90
+ console.log(chalk.green("All good"));
122
91
  }
@@ -19,25 +19,15 @@ export async function sync() {
19
19
  absolute: true,
20
20
  });
21
21
 
22
- if (!files.length) {
23
- console.log(chalk.yellow("No files found"));
24
- return;
25
- }
26
-
27
22
  const engine = new EventraEngine(process.cwd());
28
23
 
24
+ const cache = new Map<string, { content: string; deps: string[] }>();
29
25
  const toScan = new Set<string>();
30
26
 
31
- const cache = new Map<
32
- string,
33
- { content: string; deps: string[] }
34
- >();
35
-
36
- // COLLECT + CACHE
27
+ // COLLECT
37
28
  for (const file of files) {
38
29
  try {
39
30
  const raw = await fs.readFile(file, "utf-8");
40
-
41
31
  if (raw.length > 2_000_000) continue;
42
32
 
43
33
  const parsed = processFile(file, raw);
@@ -45,9 +35,7 @@ export async function sync() {
45
35
  cache.set(file, parsed);
46
36
  toScan.add(file);
47
37
 
48
- for (const dep of parsed.deps) {
49
- toScan.add(path.resolve(dep));
50
- }
38
+ parsed.deps.forEach(dep => toScan.add(path.resolve(dep)));
51
39
 
52
40
  } catch {}
53
41
  }
@@ -63,8 +51,7 @@ export async function sync() {
63
51
  cache.set(file, parsed);
64
52
  }
65
53
 
66
- const virtual = getVirtualFile(file);
67
- engine.preloadFile(virtual, parsed.content);
54
+ engine.preloadFile(getVirtualFile(file), parsed.content);
68
55
 
69
56
  } catch {}
70
57
  }
@@ -78,21 +65,16 @@ export async function sync() {
78
65
  const parsed = cache.get(file);
79
66
  if (!parsed) continue;
80
67
 
81
- const virtual = getVirtualFile(file);
82
-
83
- const res = engine.scanFile(virtual, parsed.content, config);
84
-
85
- res.detectedFunctionWrappers.forEach(w =>
86
- detectedFn.add(w)
68
+ const res = engine.updateFile(
69
+ getVirtualFile(file),
70
+ parsed.content,
71
+ config
87
72
  );
88
73
 
89
- for (const [k, v] of res.detectedComponentWrappers) {
90
- detectedCmp.set(k, v);
91
- }
74
+ res.detectedFunctionWrappers.forEach(w => detectedFn.add(w));
75
+ res.detectedComponentWrappers.forEach((v, k) => detectedCmp.set(k, v));
92
76
 
93
- } catch {
94
- console.log(chalk.gray(`skip: ${file}`));
95
- }
77
+ } catch {}
96
78
  }
97
79
 
98
80
  // RESULT
@@ -108,10 +90,7 @@ export async function sync() {
108
90
 
109
91
  config.wrappers = mergeUnique(
110
92
  config.wrappers ?? [],
111
- [...detectedCmp.entries()].map(([name, prop]) => ({
112
- name,
113
- prop,
114
- })),
93
+ [...detectedCmp.entries()].map(([name, prop]) => ({ name, prop })),
115
94
  w => `${w.name}:${w.prop}`
116
95
  );
117
96
 
@@ -120,17 +99,8 @@ export async function sync() {
120
99
  console.log(chalk.green(`Found ${events.length} events`));
121
100
  }
122
101
 
123
- // HELPERS
124
- function mergeUnique<T>(
125
- a: T[],
126
- b: T[],
127
- key: (v: T) => string
128
- ): T[] {
102
+ function mergeUnique<T>(a: T[], b: T[], key: (v: T) => string): T[] {
129
103
  const map = new Map<string, T>();
130
-
131
- [...b, ...a].forEach(item => {
132
- map.set(key(item), item);
133
- });
134
-
104
+ [...b, ...a].forEach(item => map.set(key(item), item));
135
105
  return [...map.values()];
136
106
  }