@eventra_dev/eventra-cli 0.1.28 → 0.1.29

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.
@@ -9,6 +9,7 @@ const resolver_1 = require("./resolver");
9
9
  const callResolver_1 = require("./callResolver");
10
10
  const exportResolver_1 = require("./exportResolver");
11
11
  const functionAnalyzer_1 = require("./functionAnalyzer");
12
+ const boundary_1 = require("./boundary");
12
13
  const trackingDetector_1 = require("./trackingDetector");
13
14
  function getCallName(expr) {
14
15
  if (typescript_1.default.isIdentifier(expr))
@@ -17,23 +18,6 @@ function getCallName(expr) {
17
18
  return expr.name.text;
18
19
  return "";
19
20
  }
20
- function getJsxAttrName(name) {
21
- if (typescript_1.default.isIdentifier(name))
22
- return name.text;
23
- if (typescript_1.default.isJsxNamespacedName(name))
24
- return name.name.text;
25
- return null;
26
- }
27
- // unwrap
28
- function unwrap(expr) {
29
- while (typescript_1.default.isAsExpression(expr) ||
30
- typescript_1.default.isParenthesizedExpression(expr) ||
31
- typescript_1.default.isTypeAssertionExpression(expr) ||
32
- typescript_1.default.isNonNullExpression(expr)) {
33
- expr = expr.expression;
34
- }
35
- return expr;
36
- }
37
21
  function isValidEvent(v) {
38
22
  return (v &&
39
23
  v.length > 1 &&
@@ -41,29 +25,29 @@ function isValidEvent(v) {
41
25
  !v.includes(" ") &&
42
26
  /^[a-zA-Z0-9._:-]+$/.test(v));
43
27
  }
44
- function isPropUsedInTracking(fn, checker, prop) {
45
- let found = false;
46
- function visit(node) {
47
- if (typescript_1.default.isCallExpression(node)) {
48
- if (!(0, trackingDetector_1.isTrackingCall)(node, checker)) {
49
- typescript_1.default.forEachChild(node, visit);
50
- return;
51
- }
52
- for (const arg of node.arguments) {
53
- const a = unwrap(arg);
54
- if ((typescript_1.default.isIdentifier(a) && a.text === prop) ||
55
- (typescript_1.default.isPropertyAccessExpression(a) &&
56
- a.name.text === prop)) {
57
- found = true;
58
- }
59
- }
60
- }
61
- typescript_1.default.forEachChild(node, visit);
28
+ function resolveFunctionDeep(expr, checker) {
29
+ let fn = (0, callResolver_1.resolveFunctionFromCall)(expr, checker);
30
+ if (fn)
31
+ return fn;
32
+ let symbol = checker.getSymbolAtLocation(expr);
33
+ if (!symbol)
34
+ return null;
35
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
36
+ symbol = checker.getAliasedSymbol(symbol);
62
37
  }
63
- if (fn.body) {
64
- visit(fn.body);
38
+ const resolvedSymbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
39
+ for (const decl of resolvedSymbol.getDeclarations() ?? []) {
40
+ if (typescript_1.default.isFunctionDeclaration(decl)) {
41
+ return decl;
42
+ }
43
+ if (typescript_1.default.isVariableDeclaration(decl) &&
44
+ decl.initializer &&
45
+ (typescript_1.default.isArrowFunction(decl.initializer) ||
46
+ typescript_1.default.isFunctionExpression(decl.initializer))) {
47
+ return decl.initializer;
48
+ }
65
49
  }
66
- return found;
50
+ return null;
67
51
  }
68
52
  function scanSource(source, checker, config, visited = new Set(), depth = 0, inheritedParamMap) {
69
53
  const events = new Set();
@@ -79,13 +63,15 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
79
63
  const name = getCallName(node.expression);
80
64
  const isKnownWrapper = config.functionWrappers.some(w => w.name === name);
81
65
  const isTrackingByType = (0, trackingDetector_1.isTrackingCall)(node, checker);
82
- let resolvedFn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
66
+ const resolvedFn = resolveFunctionDeep(node.expression, checker);
83
67
  const isAutoWrapper = resolvedFn && (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn) !== null;
84
68
  let isTracking = isKnownWrapper ||
85
69
  isTrackingByType ||
86
- isAutoWrapper ||
87
- name.startsWith("$");
88
- // fallback track(...)
70
+ name.startsWith("$") ||
71
+ (isAutoWrapper &&
72
+ resolvedFn &&
73
+ resolvedFn.getSourceFile &&
74
+ !(0, boundary_1.isExternalFile)(resolvedFn.getSourceFile().fileName));
89
75
  if (!isTracking) {
90
76
  if (typescript_1.default.isPropertyAccessExpression(node.expression) &&
91
77
  node.expression.name.text === "track") {
@@ -100,7 +86,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
100
86
  typescript_1.default.forEachChild(node, visit);
101
87
  return;
102
88
  }
103
- // DETECT WRAPPERS
89
+ // DETECT WRAPPER
104
90
  if (!isKnownWrapper &&
105
91
  isAutoWrapper &&
106
92
  !isTrackingByType &&
@@ -119,8 +105,7 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
119
105
  }
120
106
  });
121
107
  }
122
- let handled = false;
123
- // MAIN RESOLVE
108
+ // STRICT EVENT RESOLVE
124
109
  if (resolvedFn) {
125
110
  const idx = (0, functionAnalyzer_1.findTrackParamIndex)(resolvedFn);
126
111
  if (idx !== null && node.arguments[idx]) {
@@ -129,90 +114,17 @@ function scanSource(source, checker, config, visited = new Set(), depth = 0, inh
129
114
  if (isValidEvent(v))
130
115
  events.add(v);
131
116
  });
132
- handled = true;
133
- }
134
- }
135
- // fallback
136
- if (!handled) {
137
- for (const arg of node.arguments) {
138
- const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
139
- res.values.forEach(v => {
140
- if (isValidEvent(v))
141
- events.add(v);
142
- });
143
117
  }
144
118
  }
145
- // CROSS FILE
119
+ // CROSS-FILE
146
120
  if (resolvedFn) {
147
- const sf = resolvedFn.getSourceFile();
148
- if (!visited.has(sf.fileName)) {
121
+ const sf = resolvedFn.getSourceFile?.();
122
+ if (sf && !visited.has(sf.fileName)) {
149
123
  const res = scanSource(sf, checker, config, visited, depth + 1, paramMap);
150
124
  res.events.forEach(e => events.add(e));
151
125
  }
152
126
  }
153
127
  }
154
- // JSX
155
- if (typescript_1.default.isJsxSelfClosingElement(node) ||
156
- typescript_1.default.isJsxOpeningElement(node)) {
157
- const tagNode = typescript_1.default.isPropertyAccessExpression(node.tagName)
158
- ? node.tagName.name
159
- : node.tagName;
160
- let symbol = checker.getSymbolAtLocation(tagNode);
161
- if (symbol) {
162
- symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
163
- }
164
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
165
- symbol = checker.getAliasedSymbol(symbol);
166
- }
167
- if (!symbol) {
168
- typescript_1.default.forEachChild(node, visit);
169
- return;
170
- }
171
- const tagName = typescript_1.default.isPropertyAccessExpression(node.tagName)
172
- ? node.tagName.name.getText()
173
- : node.tagName.getText();
174
- for (const decl of symbol.getDeclarations() ?? []) {
175
- let fn = null;
176
- if (typescript_1.default.isFunctionDeclaration(decl))
177
- fn = decl;
178
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
179
- const init = decl.initializer;
180
- if (typescript_1.default.isArrowFunction(init) ||
181
- typescript_1.default.isFunctionExpression(init)) {
182
- fn = init;
183
- }
184
- }
185
- if (!fn)
186
- continue;
187
- const param = fn.parameters[0];
188
- if (!param || !typescript_1.default.isObjectBindingPattern(param.name))
189
- continue;
190
- for (const el of param.name.elements) {
191
- const prop = el.name.getText();
192
- const isEvent = isPropUsedInTracking(fn, checker, prop) ||
193
- config.wrappers.some(w => w.name === tagName && w.prop === prop);
194
- if (!isEvent)
195
- continue;
196
- const attr = node.attributes.properties.find(p => {
197
- if (!typescript_1.default.isJsxAttribute(p))
198
- return false;
199
- return getJsxAttrName(p.name) === prop;
200
- });
201
- if (!attr || !attr.initializer)
202
- continue;
203
- const expr = typescript_1.default.isJsxExpression(attr.initializer)
204
- ? attr.initializer.expression
205
- : attr.initializer;
206
- if (!expr)
207
- continue;
208
- const paramMap = new Map();
209
- paramMap.set(prop, expr);
210
- const res = scanSource(fn.getSourceFile(), checker, config, visited, depth + 1, paramMap);
211
- res.events.forEach(e => events.add(e));
212
- detectedComponentWrappers.set(tagName, prop);
213
- }
214
- }
215
- }
216
128
  typescript_1.default.forEachChild(node, visit);
217
129
  }
218
130
  visit(source);
@@ -59,14 +59,13 @@ function extractJS(input) {
59
59
  }
60
60
  return result;
61
61
  }
62
- // Extract inline handlers (onclick="...")
62
+ // Extract inline HTML handlers
63
63
  function extractInlineHandlers(input) {
64
64
  const result = [];
65
- const regex = /\bon\w+\s*=\s*["']([^"']+)["']/g;
66
- let match;
67
- while ((match = regex.exec(input))) {
68
- const code = match[1].trim();
69
- if (code && /[a-zA-Z0-9_$]\s*\(/.test(code)) {
65
+ const matches = input.matchAll(/\bon\w+\s*=\s*["']([^"']+)["']/g);
66
+ for (const m of matches) {
67
+ const code = m[1];
68
+ if (/[a-zA-Z0-9_$]\s*\(/.test(code)) {
70
69
  result.push(code);
71
70
  }
72
71
  }
@@ -76,37 +75,20 @@ function extractInlineHandlers(input) {
76
75
  function extractDynamicImports(input, file) {
77
76
  const deps = [];
78
77
  const calls = [];
79
- const regex = /import\(\s*["'](.+?)["']\s*\)/g;
80
- let match;
81
- while ((match = regex.exec(input))) {
82
- const mod = match[1];
78
+ const matches = input.matchAll(/import\(\s*["'](.+?)["']\s*\)/g);
79
+ for (const m of matches) {
80
+ const mod = m[1];
83
81
  const resolved = mod.startsWith("/")
84
82
  ? path_1.default.resolve(process.cwd(), "." + mod)
85
83
  : path_1.default.resolve(path_1.default.dirname(file), mod);
86
84
  deps.push(resolved);
87
- }
88
- // also extract calls chained after import
89
- const callRegex = /import\([^)]+\)\.then\(([^)]+)\)/g;
90
- while ((match = callRegex.exec(input))) {
91
- calls.push(match[1]);
85
+ calls.push(`import("${mod}")`);
92
86
  }
93
87
  return { deps, calls };
94
88
  }
95
- // Loose JS extractor
96
- function extractLooseJS(input) {
97
- const result = [];
98
- const lines = input.split("\n");
99
- for (const line of lines) {
100
- const l = line.trim();
101
- if (!l)
102
- continue;
103
- if (l.startsWith("<"))
104
- continue;
105
- if (/[a-zA-Z0-9_$]\s*\(/.test(l)) {
106
- result.push(l);
107
- }
108
- }
109
- return result;
89
+ // Filter only probable tracking-related code
90
+ function isProbablyTracking(code) {
91
+ return /track|event|analytics/i.test(code);
110
92
  }
111
93
  // UNIVERSAL PARSER
112
94
  function parseUniversal(content, file) {
@@ -137,20 +119,20 @@ function parseUniversal(content, file) {
137
119
  parts.push(m[2]);
138
120
  }
139
121
  }
140
- // REMOVE scripts
122
+ // REMOVE SCRIPTS → TEMPLATE PART
141
123
  const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
142
124
  // INLINE HANDLERS
143
- parts.push(...extractInlineHandlers(noScripts));
125
+ parts.push(...extractInlineHandlers(noScripts).filter(isProbablyTracking));
144
126
  // DYNAMIC IMPORTS
145
127
  const dyn = extractDynamicImports(clean, file);
146
128
  deps.push(...dyn.deps);
147
- parts.push(...dyn.calls);
148
- // LOOSE JS
149
- parts.push(...extractLooseJS(noScripts));
129
+ parts.push(...dyn.calls.filter(isProbablyTracking));
150
130
  // EXPRESSIONS
151
131
  const extracted = extractJS(clean);
152
132
  for (const e of extracted) {
153
- parts.push(e + ";");
133
+ if (isProbablyTracking(e)) {
134
+ parts.push(e + ";");
135
+ }
154
136
  }
155
137
  return {
156
138
  code: [...new Set(parts)].join("\n"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -13,25 +13,6 @@ function getCallName(expr: ts.Expression): string {
13
13
  return "";
14
14
  }
15
15
 
16
- function getJsxAttrName(name: ts.JsxAttributeName): string | null {
17
- if (ts.isIdentifier(name)) return name.text;
18
- if (ts.isJsxNamespacedName(name)) return name.name.text;
19
- return null;
20
- }
21
-
22
- // unwrap
23
- function unwrap(expr: ts.Expression): ts.Expression {
24
- while (
25
- ts.isAsExpression(expr) ||
26
- ts.isParenthesizedExpression(expr) ||
27
- ts.isTypeAssertionExpression(expr) ||
28
- ts.isNonNullExpression(expr)
29
- ) {
30
- expr = expr.expression;
31
- }
32
- return expr;
33
- }
34
-
35
16
  function isValidEvent(v: string) {
36
17
  return (
37
18
  v &&
@@ -42,41 +23,40 @@ function isValidEvent(v: string) {
42
23
  );
43
24
  }
44
25
 
45
- function isPropUsedInTracking(
46
- fn: ts.FunctionLikeDeclaration,
47
- checker: ts.TypeChecker,
48
- prop: string
49
- ) {
50
- let found = false;
51
-
52
- function visit(node: ts.Node) {
53
- if (ts.isCallExpression(node)) {
54
- if (!isTrackingCall(node, checker)) {
55
- ts.forEachChild(node, visit);
56
- return;
57
- }
26
+ function resolveFunctionDeep(
27
+ expr: ts.Expression,
28
+ checker: ts.TypeChecker
29
+ ): ts.FunctionLikeDeclaration | null {
30
+ let fn = resolveFunctionFromCall(expr, checker);
31
+ if (fn) return fn;
58
32
 
59
- for (const arg of node.arguments) {
60
- const a = unwrap(arg as ts.Expression);
33
+ let symbol = checker.getSymbolAtLocation(expr);
61
34
 
62
- if (
63
- (ts.isIdentifier(a) && a.text === prop) ||
64
- (ts.isPropertyAccessExpression(a) &&
65
- a.name.text === prop)
66
- ) {
67
- found = true;
68
- }
69
- }
70
- }
35
+ if (!symbol) return null;
71
36
 
72
- ts.forEachChild(node, visit);
37
+ if (symbol.flags & ts.SymbolFlags.Alias) {
38
+ symbol = checker.getAliasedSymbol(symbol);
73
39
  }
74
40
 
75
- if (fn.body) {
76
- visit(fn.body);
41
+ const resolvedSymbol =
42
+ resolveExportedSymbol(symbol, checker) ?? symbol;
43
+
44
+ for (const decl of resolvedSymbol.getDeclarations() ?? []) {
45
+ if (ts.isFunctionDeclaration(decl)) {
46
+ return decl;
47
+ }
48
+
49
+ if (
50
+ ts.isVariableDeclaration(decl) &&
51
+ decl.initializer &&
52
+ (ts.isArrowFunction(decl.initializer) ||
53
+ ts.isFunctionExpression(decl.initializer))
54
+ ) {
55
+ return decl.initializer;
56
+ }
77
57
  }
78
58
 
79
- return found;
59
+ return null;
80
60
  }
81
61
 
82
62
  export type ScanResult = {
@@ -114,7 +94,10 @@ export function scanSource(
114
94
 
115
95
  const isTrackingByType = isTrackingCall(node, checker);
116
96
 
117
- let resolvedFn = resolveFunctionFromCall(node.expression, checker);
97
+ const resolvedFn = resolveFunctionDeep(
98
+ node.expression,
99
+ checker
100
+ );
118
101
 
119
102
  const isAutoWrapper =
120
103
  resolvedFn && findTrackParamIndex(resolvedFn) !== null;
@@ -122,10 +105,12 @@ export function scanSource(
122
105
  let isTracking =
123
106
  isKnownWrapper ||
124
107
  isTrackingByType ||
125
- isAutoWrapper ||
126
- name.startsWith("$");
108
+ name.startsWith("$") ||
109
+ (isAutoWrapper &&
110
+ resolvedFn &&
111
+ resolvedFn.getSourceFile &&
112
+ !isExternalFile(resolvedFn.getSourceFile().fileName));
127
113
 
128
- // fallback track(...)
129
114
  if (!isTracking) {
130
115
  if (
131
116
  ts.isPropertyAccessExpression(node.expression) &&
@@ -145,7 +130,7 @@ export function scanSource(
145
130
  return;
146
131
  }
147
132
 
148
- // DETECT WRAPPERS
133
+ // DETECT WRAPPER
149
134
  if (
150
135
  !isKnownWrapper &&
151
136
  isAutoWrapper &&
@@ -169,9 +154,7 @@ export function scanSource(
169
154
  });
170
155
  }
171
156
 
172
- let handled = false;
173
-
174
- // MAIN RESOLVE
157
+ // STRICT EVENT RESOLVE
175
158
  if (resolvedFn) {
176
159
  const idx = findTrackParamIndex(resolvedFn);
177
160
 
@@ -185,27 +168,14 @@ export function scanSource(
185
168
  res.values.forEach(v => {
186
169
  if (isValidEvent(v)) events.add(v);
187
170
  });
188
-
189
- handled = true;
190
- }
191
- }
192
-
193
- // fallback
194
- if (!handled) {
195
- for (const arg of node.arguments) {
196
- const res = resolveNodeValue(arg, checker, paramMap);
197
-
198
- res.values.forEach(v => {
199
- if (isValidEvent(v)) events.add(v);
200
- });
201
171
  }
202
172
  }
203
173
 
204
- // CROSS FILE
174
+ // CROSS-FILE
205
175
  if (resolvedFn) {
206
- const sf = resolvedFn.getSourceFile();
176
+ const sf = resolvedFn.getSourceFile?.();
207
177
 
208
- if (!visited.has(sf.fileName)) {
178
+ if (sf && !visited.has(sf.fileName)) {
209
179
  const res = scanSource(
210
180
  sf,
211
181
  checker,
@@ -220,100 +190,6 @@ export function scanSource(
220
190
  }
221
191
  }
222
192
 
223
- // JSX
224
- if (
225
- ts.isJsxSelfClosingElement(node) ||
226
- ts.isJsxOpeningElement(node)
227
- ) {
228
- const tagNode =
229
- ts.isPropertyAccessExpression(node.tagName)
230
- ? node.tagName.name
231
- : node.tagName;
232
-
233
- let symbol = checker.getSymbolAtLocation(tagNode);
234
-
235
- if (symbol) {
236
- symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
237
- }
238
-
239
- if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
240
- symbol = checker.getAliasedSymbol(symbol);
241
- }
242
-
243
- if (!symbol) {
244
- ts.forEachChild(node, visit);
245
- return;
246
- }
247
-
248
- const tagName =
249
- ts.isPropertyAccessExpression(node.tagName)
250
- ? node.tagName.name.getText()
251
- : node.tagName.getText();
252
-
253
- for (const decl of symbol.getDeclarations() ?? []) {
254
- let fn: ts.FunctionLikeDeclaration | null = null;
255
-
256
- if (ts.isFunctionDeclaration(decl)) fn = decl;
257
-
258
- if (ts.isVariableDeclaration(decl) && decl.initializer) {
259
- const init = decl.initializer;
260
-
261
- if (
262
- ts.isArrowFunction(init) ||
263
- ts.isFunctionExpression(init)
264
- ) {
265
- fn = init;
266
- }
267
- }
268
-
269
- if (!fn) continue;
270
-
271
- const param = fn.parameters[0];
272
- if (!param || !ts.isObjectBindingPattern(param.name)) continue;
273
-
274
- for (const el of param.name.elements) {
275
- const prop = el.name.getText();
276
-
277
- const isEvent =
278
- isPropUsedInTracking(fn, checker, prop) ||
279
- config.wrappers.some(
280
- w => w.name === tagName && w.prop === prop
281
- );
282
-
283
- if (!isEvent) continue;
284
-
285
- const attr = node.attributes.properties.find(p => {
286
- if (!ts.isJsxAttribute(p)) return false;
287
- return getJsxAttrName(p.name) === prop;
288
- }) as ts.JsxAttribute | undefined;
289
-
290
- if (!attr || !attr.initializer) continue;
291
-
292
- const expr = ts.isJsxExpression(attr.initializer)
293
- ? attr.initializer.expression
294
- : attr.initializer;
295
-
296
- if (!expr) continue;
297
-
298
- const paramMap = new Map<string, ts.Expression>();
299
- paramMap.set(prop, expr);
300
-
301
- const res = scanSource(
302
- fn.getSourceFile(),
303
- checker,
304
- config,
305
- visited,
306
- depth + 1,
307
- paramMap
308
- );
309
-
310
- res.events.forEach(e => events.add(e));
311
-
312
- detectedComponentWrappers.set(tagName, prop);
313
- }
314
- }
315
- }
316
-
317
193
  ts.forEachChild(node, visit);
318
194
  }
319
195
 
@@ -64,17 +64,17 @@ function extractJS(input: string): string[] {
64
64
  return result;
65
65
  }
66
66
 
67
- // Extract inline handlers (onclick="...")
67
+ // Extract inline HTML handlers
68
68
  function extractInlineHandlers(input: string): string[] {
69
69
  const result: string[] = [];
70
70
 
71
- const regex = /\bon\w+\s*=\s*["']([^"']+)["']/g;
72
-
73
- let match;
74
- while ((match = regex.exec(input))) {
75
- const code = match[1].trim();
71
+ const matches = input.matchAll(
72
+ /\bon\w+\s*=\s*["']([^"']+)["']/g
73
+ );
76
74
 
77
- if (code && /[a-zA-Z0-9_$]\s*\(/.test(code)) {
75
+ for (const m of matches) {
76
+ const code = m[1];
77
+ if (/[a-zA-Z0-9_$]\s*\(/.test(code)) {
78
78
  result.push(code);
79
79
  }
80
80
  }
@@ -90,47 +90,27 @@ function extractDynamicImports(
90
90
  const deps: string[] = [];
91
91
  const calls: string[] = [];
92
92
 
93
- const regex = /import\(\s*["'](.+?)["']\s*\)/g;
93
+ const matches = input.matchAll(
94
+ /import\(\s*["'](.+?)["']\s*\)/g
95
+ );
94
96
 
95
- let match;
96
- while ((match = regex.exec(input))) {
97
- const mod = match[1];
97
+ for (const m of matches) {
98
+ const mod = m[1];
98
99
 
99
100
  const resolved = mod.startsWith("/")
100
101
  ? path.resolve(process.cwd(), "." + mod)
101
102
  : path.resolve(path.dirname(file), mod);
102
103
 
103
104
  deps.push(resolved);
104
- }
105
-
106
- // also extract calls chained after import
107
- const callRegex = /import\([^)]+\)\.then\(([^)]+)\)/g;
108
-
109
- while ((match = callRegex.exec(input))) {
110
- calls.push(match[1]);
105
+ calls.push(`import("${mod}")`);
111
106
  }
112
107
 
113
108
  return { deps, calls };
114
109
  }
115
110
 
116
- // Loose JS extractor
117
- function extractLooseJS(input: string): string[] {
118
- const result: string[] = [];
119
-
120
- const lines = input.split("\n");
121
-
122
- for (const line of lines) {
123
- const l = line.trim();
124
-
125
- if (!l) continue;
126
- if (l.startsWith("<")) continue;
127
-
128
- if (/[a-zA-Z0-9_$]\s*\(/.test(l)) {
129
- result.push(l);
130
- }
131
- }
132
-
133
- return result;
111
+ // Filter only probable tracking-related code
112
+ function isProbablyTracking(code: string): boolean {
113
+ return /track|event|analytics/i.test(code);
134
114
  }
135
115
 
136
116
  // UNIVERSAL PARSER
@@ -179,27 +159,29 @@ export function parseUniversal(
179
159
  }
180
160
  }
181
161
 
182
- // REMOVE scripts
162
+ // REMOVE SCRIPTS → TEMPLATE PART
183
163
  const noScripts = clean.replace(
184
164
  /<script[\s\S]*?<\/script>/g,
185
165
  ""
186
166
  );
187
167
 
188
168
  // INLINE HANDLERS
189
- parts.push(...extractInlineHandlers(noScripts));
169
+ parts.push(
170
+ ...extractInlineHandlers(noScripts).filter(isProbablyTracking)
171
+ );
190
172
 
191
173
  // DYNAMIC IMPORTS
192
174
  const dyn = extractDynamicImports(clean, file);
193
175
  deps.push(...dyn.deps);
194
- parts.push(...dyn.calls);
195
-
196
- // LOOSE JS
197
- parts.push(...extractLooseJS(noScripts));
176
+ parts.push(...dyn.calls.filter(isProbablyTracking));
198
177
 
199
178
  // EXPRESSIONS
200
179
  const extracted = extractJS(clean);
180
+
201
181
  for (const e of extracted) {
202
- parts.push(e + ";");
182
+ if (isProbablyTracking(e)) {
183
+ parts.push(e + ";");
184
+ }
203
185
  }
204
186
 
205
187
  return {