@eventra_dev/eventra-cli 0.1.31 → 0.1.33

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.
@@ -173,15 +173,13 @@ export function scanSource(
173
173
  config: EventraConfig,
174
174
  visited = new Set<string>(),
175
175
  inheritedParamMap?: Map<string, ts.Expression>,
176
- wrapperCache?: Map<string, string | null>
176
+ wrapperCache = new Map<string, string | null>()
177
177
  ): ScanResult {
178
- wrapperCache = wrapperCache ?? new Map();
179
-
180
178
  const events = new Set<string>();
181
179
  const detectedFunctionWrappers = new Set<string>();
182
180
  const detectedComponentWrappers = new Map<string, string>();
183
181
 
184
- if (visited.size > 200 || visited.has(source.fileName)) {
182
+ if (visited.has(source.fileName) || visited.size > 200) {
185
183
  return { events, detectedFunctionWrappers, detectedComponentWrappers };
186
184
  }
187
185
 
@@ -189,76 +187,58 @@ export function scanSource(
189
187
 
190
188
  function visit(node: ts.Node) {
191
189
  // JSX WRAPPERS
192
- if (
193
- ts.isJsxSelfClosingElement(node) ||
194
- ts.isJsxOpeningElement(node)
195
- ) {
190
+ if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) {
196
191
  const tagName = getJsxTagName(node.tagName);
197
192
 
198
193
  // skip html
199
- if (!tagName || tagName[0] === tagName[0].toLowerCase()) {
194
+ if (tagName[0] === tagName[0].toLowerCase()) {
200
195
  ts.forEachChild(node, visit);
201
196
  return;
202
197
  }
203
198
 
204
- let wrapper = config.wrappers.find(
205
- (w) => w.name === tagName
206
- );
199
+ let wrapper = config.wrappers.find(w => w.name === tagName);
207
200
 
201
+ // AUTO DETECT
208
202
  if (!wrapper) {
209
- let symbol = checker.getSymbolAtLocation(node.tagName);
210
-
211
- let cacheKey = tagName;
203
+ if (wrapperCache.has(tagName)) {
204
+ const cached = wrapperCache.get(tagName);
205
+ if (cached) wrapper = { name: tagName, prop: cached };
206
+ } else {
207
+ let symbol = checker.getSymbolAtLocation(node.tagName);
212
208
 
213
- if (symbol) {
214
- if (symbol.flags & ts.SymbolFlags.Alias) {
209
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
215
210
  symbol = checker.getAliasedSymbol(symbol);
216
211
  }
217
212
 
218
- symbol =
219
- resolveExportedSymbol(symbol, checker) ?? symbol;
220
-
221
- cacheKey = checker.getFullyQualifiedName(symbol);
222
- }
223
-
224
- if (wrapperCache?.has(cacheKey)) {
225
- const cached = wrapperCache.get(cacheKey);
226
- if (cached) {
227
- wrapper = { name: tagName, prop: cached };
228
- }
229
- } else if (symbol) {
230
- let detected: string | null = null;
213
+ if (symbol) {
214
+ symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
231
215
 
232
- for (const decl of symbol.getDeclarations() ?? []) {
233
- let fn: ts.FunctionLikeDeclaration | null = null;
216
+ for (const decl of symbol.getDeclarations() ?? []) {
217
+ let fn: ts.FunctionLikeDeclaration | null = null;
234
218
 
235
- if (ts.isFunctionDeclaration(decl)) fn = decl;
219
+ if (ts.isFunctionDeclaration(decl)) fn = decl;
236
220
 
237
- if (
238
- ts.isVariableDeclaration(decl) &&
239
- decl.initializer &&
240
- (ts.isArrowFunction(decl.initializer) ||
241
- ts.isFunctionExpression(decl.initializer))
242
- ) {
243
- fn = decl.initializer;
244
- }
221
+ if (
222
+ ts.isVariableDeclaration(decl) &&
223
+ decl.initializer &&
224
+ (ts.isArrowFunction(decl.initializer) ||
225
+ ts.isFunctionExpression(decl.initializer))
226
+ ) {
227
+ fn = decl.initializer;
228
+ }
245
229
 
246
- if (!fn) continue;
230
+ if (!fn) continue;
247
231
 
248
- const prop = findTrackedPropName(fn, checker);
232
+ const prop = findTrackedPropName(fn, checker);
233
+ wrapperCache.set(tagName, prop ?? null);
249
234
 
250
- if (prop) {
251
- detected = prop;
252
- break;
235
+ if (prop) {
236
+ wrapper = { name: tagName, prop };
237
+ detectedComponentWrappers.set(tagName, prop);
238
+ break;
239
+ }
253
240
  }
254
241
  }
255
-
256
- wrapperCache?.set(cacheKey, detected);
257
-
258
- if (detected) {
259
- wrapper = { name: tagName, prop: detected };
260
- detectedComponentWrappers.set(tagName, detected);
261
- }
262
242
  }
263
243
  }
264
244
 
@@ -268,25 +248,21 @@ export function scanSource(
268
248
 
269
249
  const name = getJsxAttrName(attr.name);
270
250
  if (name !== wrapper.prop) continue;
251
+
271
252
  if (!attr.initializer) continue;
272
253
 
273
254
  if (ts.isStringLiteral(attr.initializer)) {
274
- if (isValidEvent(attr.initializer.text)) {
275
- events.add(attr.initializer.text);
276
- }
255
+ const v = attr.initializer.text;
256
+ if (isValidEvent(v)) events.add(v);
277
257
  }
278
258
 
279
259
  if (ts.isJsxExpression(attr.initializer)) {
280
260
  const expr = attr.initializer.expression;
281
261
  if (!expr) continue;
282
262
 
283
- const res = resolveNodeValue(
284
- expr,
285
- checker,
286
- inheritedParamMap
287
- );
263
+ const res = resolveNodeValue(expr, checker, inheritedParamMap);
288
264
 
289
- res.values.forEach((v) => {
265
+ res.values.forEach(v => {
290
266
  if (isValidEvent(v)) events.add(v);
291
267
  });
292
268
  }
@@ -297,40 +273,56 @@ export function scanSource(
297
273
  return;
298
274
  }
299
275
 
300
- // FUNCTION TRACKING
276
+ // FUNCTION CALLS
301
277
  if (ts.isCallExpression(node)) {
302
- const name = getCallName(node.expression);
278
+ let trackParamIndex: number | null = null;
279
+ let isTracking = false;
280
+
281
+ const callName = getCallName(node.expression);
282
+
283
+ // direct .track()
284
+ if (
285
+ ts.isPropertyAccessExpression(node.expression) &&
286
+ node.expression.name.text === "track"
287
+ ) {
288
+ isTracking = true;
289
+ trackParamIndex = 0;
290
+ }
303
291
 
304
- const isKnownWrapper = config.functionWrappers.some(
305
- (w) => w.name === name
292
+ // config wrapper
293
+ const manualWrapper = config.functionWrappers.find(
294
+ w => w.name === callName
306
295
  );
307
296
 
297
+ if (manualWrapper) {
298
+ isTracking = true;
299
+ trackParamIndex = 0;
300
+ }
301
+
302
+ // resolve function → check body
308
303
  const resolvedFn = resolveFunctionFromCall(
309
304
  node.expression,
310
305
  checker
311
306
  );
312
307
 
313
- const isAutoWrapper =
314
- !!resolvedFn && findTrackParamIndex(resolvedFn) !== null;
308
+ if (resolvedFn) {
309
+ const idx = findTrackParamIndex(resolvedFn);
315
310
 
316
- const isTracking =
317
- name === "track" ||
318
- isKnownWrapper ||
319
- (isAutoWrapper &&
320
- resolvedFn &&
321
- !isExternalFile(
322
- resolvedFn.getSourceFile().fileName
323
- ));
311
+ if (idx !== null) {
312
+ isTracking = true;
313
+ trackParamIndex = idx;
314
+
315
+ if (!manualWrapper && callName !== "track") {
316
+ detectedFunctionWrappers.add(callName);
317
+ }
318
+ }
319
+ }
324
320
 
325
321
  if (!isTracking) {
326
322
  ts.forEachChild(node, visit);
327
323
  return;
328
324
  }
329
325
 
330
- if (!isKnownWrapper && isAutoWrapper && name !== "track") {
331
- detectedFunctionWrappers.add(name);
332
- }
333
-
334
326
  const paramMap = new Map(inheritedParamMap ?? []);
335
327
 
336
328
  if (resolvedFn && ts.isFunctionLike(resolvedFn)) {
@@ -344,22 +336,19 @@ export function scanSource(
344
336
  });
345
337
  }
346
338
 
347
- if (resolvedFn) {
348
- const idx = findTrackParamIndex(resolvedFn);
339
+ if (trackParamIndex !== null && node.arguments[trackParamIndex]) {
340
+ const res = resolveNodeValue(
341
+ node.arguments[trackParamIndex],
342
+ checker,
343
+ paramMap
344
+ );
349
345
 
350
- if (idx !== null && node.arguments[idx]) {
351
- const res = resolveNodeValue(
352
- node.arguments[idx],
353
- checker,
354
- paramMap
355
- );
356
-
357
- res.values.forEach((v) => {
358
- if (isValidEvent(v)) events.add(v);
359
- });
360
- }
346
+ res.values.forEach(v => {
347
+ if (isValidEvent(v)) events.add(v);
348
+ });
361
349
  }
362
350
 
351
+ // CROSS FILE
363
352
  if (resolvedFn) {
364
353
  const sf = resolvedFn.getSourceFile();
365
354
 
@@ -373,7 +362,7 @@ export function scanSource(
373
362
  wrapperCache
374
363
  );
375
364
 
376
- res.events.forEach((e) => events.add(e));
365
+ res.events.forEach(e => events.add(e));
377
366
  }
378
367
  }
379
368
  }
@@ -1,28 +1,48 @@
1
1
  import path from "path";
2
2
  import { ParseResult } from "../types";
3
3
 
4
- // SAFE COMMENT STRIP
4
+ /* ---------------------------------- */
5
+ /* COMMENT STRIP (SAFE) */
6
+ /* ---------------------------------- */
5
7
  function stripComments(input: string): string {
6
8
  return input
7
9
  .replace(/<!--[\s\S]*?-->/g, "")
8
10
  .replace(/\/\*[\s\S]*?\*\//g, "");
9
11
  }
10
12
 
11
- // TRACK FILTER
13
+ /* ---------------------------------- */
14
+ /* TRACK DETECTOR */
15
+ /* ---------------------------------- */
12
16
  function looksLikeTracking(code: string) {
13
- return /\btrack\w*\s*\(/.test(code);
17
+ return /\btrack\s*\(|\.track\s*\(/.test(code);
14
18
  }
15
19
 
16
- // SAFE {} PARSER
20
+ /* ---------------------------------- */
21
+ /* SAFE {} PARSER (JSX / TEMPLATES) */
22
+ /* ---------------------------------- */
17
23
  function extractBracedExpressions(input: string): string[] {
18
24
  const result: string[] = [];
19
25
 
20
26
  let depth = 0;
21
27
  let start = -1;
28
+ let inString: string | null = null;
22
29
 
23
30
  for (let i = 0; i < input.length; i++) {
24
31
  const char = input[i];
25
32
 
33
+ // handle strings
34
+ if (inString) {
35
+ if (char === inString && input[i - 1] !== "\\") {
36
+ inString = null;
37
+ }
38
+ continue;
39
+ }
40
+
41
+ if (char === '"' || char === "'" || char === "`") {
42
+ inString = char;
43
+ continue;
44
+ }
45
+
26
46
  if (char === "{") {
27
47
  if (depth === 0) start = i + 1;
28
48
  depth++;
@@ -30,9 +50,11 @@ function extractBracedExpressions(input: string): string[] {
30
50
  depth--;
31
51
  if (depth === 0 && start !== -1) {
32
52
  const expr = input.slice(start, i);
53
+
33
54
  if (looksLikeTracking(expr)) {
34
55
  result.push(expr);
35
56
  }
57
+
36
58
  start = -1;
37
59
  }
38
60
  }
@@ -41,7 +63,9 @@ function extractBracedExpressions(input: string): string[] {
41
63
  return result;
42
64
  }
43
65
 
44
- // HTML handlers
66
+ /* ---------------------------------- */
67
+ /* HTML HANDLERS */
68
+ /* ---------------------------------- */
45
69
  function extractHTMLHandlers(input: string): string[] {
46
70
  const result: string[] = [];
47
71
 
@@ -56,7 +80,9 @@ function extractHTMLHandlers(input: string): string[] {
56
80
  return result;
57
81
  }
58
82
 
59
- // VUE handlers
83
+ /* ---------------------------------- */
84
+ /* VUE HANDLERS */
85
+ /* ---------------------------------- */
60
86
  function extractVueHandlers(input: string): string[] {
61
87
  const result: string[] = [];
62
88
 
@@ -71,29 +97,34 @@ function extractVueHandlers(input: string): string[] {
71
97
  return result;
72
98
  }
73
99
 
74
- // SVELTE handlers
100
+ /* ---------------------------------- */
101
+ /* SVELTE HANDLERS */
102
+ /* ---------------------------------- */
75
103
  function extractSvelteHandlers(input: string): string[] {
76
104
  const result: string[] = [];
77
105
 
78
- const regex = /\bon:[\w]+\s*=\s*{([^}]+)}/g;
106
+ const regex = /\bon:[\w]+\s*=\s*{([\s\S]*?)}/g;
79
107
 
80
108
  for (const m of input.matchAll(regex)) {
81
- if (looksLikeTracking(m[1])) {
82
- result.push(m[1]);
83
- }
109
+ const expr = m[1];
110
+
111
+ const nested = extractBracedExpressions(`{${expr}}`);
112
+ result.push(...nested);
84
113
  }
85
114
 
86
115
  return result;
87
116
  }
88
117
 
89
- // SCRIPT EXTRACTION
118
+ /* ---------------------------------- */
119
+ /* SCRIPT EXTRACTION */
120
+ /* ---------------------------------- */
90
121
  function extractScripts(input: string, file: string) {
91
122
  const scripts: string[] = [];
92
123
  const deps: string[] = [];
93
124
 
94
- // src scripts
125
+ // <script src="">
95
126
  const srcRegex =
96
- /<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g;
127
+ /<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/gi;
97
128
 
98
129
  for (const m of input.matchAll(srcRegex)) {
99
130
  const src = m[1];
@@ -105,9 +136,9 @@ function extractScripts(input: string, file: string) {
105
136
  deps.push(resolved);
106
137
  }
107
138
 
108
- // inline scripts
139
+ // inline <script>
109
140
  const scriptRegex =
110
- /<script\b[^>]*>([\s\S]*?)<\/script>/g;
141
+ /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
111
142
 
112
143
  for (const m of input.matchAll(scriptRegex)) {
113
144
  if (m[1]?.trim()) {
@@ -120,9 +151,37 @@ function extractScripts(input: string, file: string) {
120
151
  return { scripts, deps, noScripts };
121
152
  }
122
153
 
123
- // DYNAMIC IMPORTS
154
+ /* ---------------------------------- */
155
+ /* STATIC IMPORTS */
156
+ /* ---------------------------------- */
157
+ function extractStaticImports(input: string, file: string) {
158
+ const deps: string[] = [];
159
+
160
+ const regex =
161
+ /import\s+(?:.+?\s+from\s+)?["'](.+?)["']/g;
162
+
163
+ for (const m of input.matchAll(regex)) {
164
+ const mod = m[1];
165
+
166
+ if (!mod.startsWith(".") && !mod.startsWith("/")) continue;
167
+
168
+ const resolved = path.resolve(
169
+ path.dirname(file),
170
+ mod
171
+ );
172
+
173
+ deps.push(resolved);
174
+ }
175
+
176
+ return deps;
177
+ }
178
+
179
+ /* ---------------------------------- */
180
+ /* DYNAMIC IMPORTS */
181
+ /* ---------------------------------- */
124
182
  function extractDynamicImports(input: string, file: string) {
125
183
  const deps: string[] = [];
184
+ const calls: string[] = [];
126
185
 
127
186
  const regex = /import\(\s*["'](.+?)["']\s*\)/g;
128
187
 
@@ -134,12 +193,18 @@ function extractDynamicImports(input: string, file: string) {
134
193
  : path.resolve(path.dirname(file), mod);
135
194
 
136
195
  deps.push(resolved);
196
+
197
+ if (looksLikeTracking(mod)) {
198
+ calls.push(`import("${mod}")`);
199
+ }
137
200
  }
138
201
 
139
- return deps;
202
+ return { deps, calls };
140
203
  }
141
204
 
142
- // MAIN
205
+ /* ---------------------------------- */
206
+ /* MAIN */
207
+ /* ---------------------------------- */
143
208
  export function parseUniversal(
144
209
  content: string,
145
210
  file: string
@@ -150,7 +215,7 @@ export function parseUniversal(
150
215
  const clean = stripComments(content);
151
216
 
152
217
  /* ---------- ASTRO FRONTMATTER ---------- */
153
- const fm = clean.match(/^---([\s\S]*?)---/);
218
+ const fm = clean.match(/^\s*---([\s\S]*?)---/);
154
219
  if (fm?.[1]) {
155
220
  parts.push(fm[1]);
156
221
  }
@@ -171,7 +236,11 @@ export function parseUniversal(
171
236
  parts.push(...extractBracedExpressions(clean));
172
237
 
173
238
  /* ---------- IMPORTS ---------- */
174
- deps.push(...extractDynamicImports(clean, file));
239
+ deps.push(...extractStaticImports(clean, file));
240
+
241
+ const dyn = extractDynamicImports(clean, file);
242
+ deps.push(...dyn.deps);
243
+ parts.push(...dyn.calls);
175
244
 
176
245
  return {
177
246
  code: [...new Set(parts)].join("\n"),