@eventra_dev/eventra-cli 0.1.21 → 0.1.23

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.
@@ -15,8 +15,20 @@ function findTrackParamIndex(fn) {
15
15
  let result = null;
16
16
  function visit(node) {
17
17
  if (typescript_1.default.isCallExpression(node)) {
18
- const name = getCallName(node.expression);
19
- if (/(track|event|capture|send)/i.test(name)) {
18
+ let isTrackCall = false;
19
+ if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
20
+ const method = node.expression.name.text;
21
+ if (method === "track") {
22
+ isTrackCall = true;
23
+ }
24
+ }
25
+ // fallback
26
+ if (typescript_1.default.isIdentifier(node.expression)) {
27
+ if (node.expression.text === "track") {
28
+ isTrackCall = true;
29
+ }
30
+ }
31
+ if (isTrackCall) {
20
32
  for (const arg of node.arguments) {
21
33
  if (typescript_1.default.isIdentifier(arg)) {
22
34
  const idx = paramIndex.get(arg.text);
@@ -25,6 +37,10 @@ function findTrackParamIndex(fn) {
25
37
  return;
26
38
  }
27
39
  }
40
+ if (typescript_1.default.isStringLiteral(arg)) {
41
+ result = 0;
42
+ return;
43
+ }
28
44
  }
29
45
  }
30
46
  }
@@ -35,11 +51,3 @@ function findTrackParamIndex(fn) {
35
51
  }
36
52
  return result;
37
53
  }
38
- function getCallName(expr) {
39
- if (typescript_1.default.isIdentifier(expr))
40
- return expr.text;
41
- if (typescript_1.default.isPropertyAccessExpression(expr)) {
42
- return expr.name.text;
43
- }
44
- return "";
45
- }
@@ -11,42 +11,60 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
11
11
  return { values: [], dynamic: true };
12
12
  }
13
13
  seen.add(node);
14
+ // param mapping
14
15
  if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
15
16
  return resolveNodeValue(paramMap.get(node.text), checker, paramMap, seen);
16
17
  }
18
+ // STRING
17
19
  if (typescript_1.default.isStringLiteral(node)) {
18
20
  return { values: [node.text], dynamic: false };
19
21
  }
22
+ // ARRAY
23
+ if (typescript_1.default.isArrayLiteralExpression(node)) {
24
+ const values = [];
25
+ for (const el of node.elements) {
26
+ const res = resolveNodeValue(el, checker, paramMap, seen);
27
+ if (res.values.length) {
28
+ values.push(...res.values);
29
+ }
30
+ }
31
+ return { values, dynamic: true };
32
+ }
33
+ // TEMPLATE
20
34
  if (typescript_1.default.isTemplateExpression(node)) {
21
- let result = node.head.text;
35
+ let results = [node.head.text];
22
36
  for (const span of node.templateSpans) {
23
- result += "*";
24
- result += span.literal.text;
37
+ const res = resolveNodeValue(span.expression, checker, paramMap, seen);
38
+ if (res.values.length) {
39
+ results = results.flatMap(r => res.values.map(v => r + v));
40
+ }
41
+ else {
42
+ results = results.map(r => r + "*");
43
+ }
44
+ results = results.map(r => r + span.literal.text);
25
45
  }
26
- return { values: [result], dynamic: true };
46
+ return { values: results, dynamic: true };
27
47
  }
48
+ // CONDITIONAL
28
49
  if (typescript_1.default.isConditionalExpression(node)) {
29
50
  const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
30
51
  const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
31
52
  return {
32
- values: [...new Set([...(a.values ?? []), ...(b.values ?? [])])],
53
+ values: [...new Set([...a.values, ...b.values])],
33
54
  dynamic: true,
34
55
  };
35
56
  }
57
+ // IDENTIFIER
36
58
  if (typescript_1.default.isIdentifier(node)) {
37
59
  let symbol = checker.getSymbolAtLocation(node);
38
60
  if (!symbol)
39
61
  return { values: [], dynamic: true };
40
- for (const decl of symbol.getDeclarations() ?? []) {
41
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
42
- return resolveNodeValue(decl.initializer, checker, paramMap, seen);
43
- }
44
- }
45
- const original = symbol;
62
+ // unwrap export
46
63
  symbol = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
47
- if (symbol !== original && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
64
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
48
65
  symbol = checker.getAliasedSymbol(symbol);
49
66
  }
67
+ // inline variable
50
68
  for (const decl of symbol.getDeclarations() ?? []) {
51
69
  if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
52
70
  return resolveNodeValue(decl.initializer, checker, paramMap, seen);
@@ -60,12 +78,30 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
60
78
  }
61
79
  }
62
80
  }
81
+ // UNION TYPES
82
+ const type = checker.getTypeAtLocation(node);
83
+ if (type.isUnion()) {
84
+ const values = [];
85
+ for (const t of type.types) {
86
+ if (t.isStringLiteral?.()) {
87
+ values.push(t.value);
88
+ }
89
+ }
90
+ if (values.length) {
91
+ return { values, dynamic: false };
92
+ }
93
+ }
94
+ // TYPE FALLBACK (as const)
95
+ if (type.isStringLiteral?.()) {
96
+ return { values: [type.value], dynamic: false };
97
+ }
63
98
  return { values: [], dynamic: true };
64
99
  }
100
+ // PROPERTY ACCESS (obj.a.b)
65
101
  if (typescript_1.default.isPropertyAccessExpression(node)) {
66
102
  const symbol = checker.getSymbolAtLocation(node);
67
103
  if (symbol) {
68
- let s = symbol;
104
+ let s = (0, exportResolver_1.resolveExportedSymbol)(symbol, checker) ?? symbol;
69
105
  if (s.flags & typescript_1.default.SymbolFlags.Alias) {
70
106
  s = checker.getAliasedSymbol(s);
71
107
  }
@@ -80,6 +116,20 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
80
116
  }
81
117
  }
82
118
  }
119
+ // constant enum / readonly
120
+ const constantValue = checker.getConstantValue(node);
121
+ if (typeof constantValue === "string") {
122
+ return { values: [constantValue], dynamic: false };
123
+ }
124
+ // type fallback
125
+ const type = checker.getTypeAtLocation(node);
126
+ if (type.isStringLiteral?.()) {
127
+ return {
128
+ values: [type.value],
129
+ dynamic: false,
130
+ };
131
+ }
132
+ // object literal resolve
83
133
  if (typescript_1.default.isIdentifier(node.expression)) {
84
134
  const objSymbol = checker.getSymbolAtLocation(node.expression);
85
135
  if (objSymbol) {
@@ -101,33 +151,59 @@ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
101
151
  }
102
152
  return { values: [], dynamic: true };
103
153
  }
154
+ // ARRAY ACCESS (arr[0])
155
+ if (typescript_1.default.isElementAccessExpression(node)) {
156
+ const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
157
+ if (!node.argumentExpression) {
158
+ return { values: obj.values, dynamic: true };
159
+ }
160
+ const index = resolveNodeValue(node.argumentExpression, checker, paramMap, seen);
161
+ // if index known
162
+ if (index.values.length === 1) {
163
+ const i = Number(index.values[0]);
164
+ if (!Number.isNaN(i) && obj.values[i] !== undefined) {
165
+ return {
166
+ values: [obj.values[i]],
167
+ dynamic: false,
168
+ };
169
+ }
170
+ }
171
+ // fallback → dynamic
172
+ return obj.values.length
173
+ ? { values: obj.values, dynamic: true }
174
+ : { values: [], dynamic: true };
175
+ }
176
+ // OBJECT
104
177
  if (typescript_1.default.isObjectLiteralExpression(node)) {
105
178
  const values = [];
106
179
  for (const prop of node.properties) {
107
180
  if (!typescript_1.default.isPropertyAssignment(prop))
108
181
  continue;
109
182
  const name = prop.name.getText();
110
- if (name === "event") {
183
+ if (["event", "name", "type"].includes(name)) {
111
184
  const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
112
185
  values.push(...res.values);
113
186
  }
114
187
  }
115
188
  return { values, dynamic: true };
116
189
  }
190
+ // BINARY ("btn_" + name)
117
191
  if (typescript_1.default.isBinaryExpression(node)) {
118
192
  if (node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
119
193
  const left = resolveNodeValue(node.left, checker, paramMap, seen);
120
194
  const right = resolveNodeValue(node.right, checker, paramMap, seen);
121
- if (left && right) {
122
- return {
123
- values: left.values.flatMap(l => right.values.length
124
- ? right.values.map(r => l + r)
125
- : [l + "*"]),
126
- dynamic: true,
127
- };
195
+ if (!left.values.length) {
196
+ return { values: [], dynamic: true };
128
197
  }
198
+ return {
199
+ values: left.values.flatMap(l => right.values.length
200
+ ? right.values.map(r => l + r)
201
+ : [l + "*"]),
202
+ dynamic: true,
203
+ };
129
204
  }
130
205
  }
206
+ // CALL → dynamic
131
207
  if (typescript_1.default.isCallExpression(node)) {
132
208
  return { values: [], dynamic: true };
133
209
  }
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.processFile = processFile;
7
7
  const path_1 = __importDefault(require("path"));
8
- const universal_1 = require("./parsers/universal");
8
+ const universal_1 = require("./universal");
9
9
  function processFile(file, content) {
10
10
  const { code, deps } = (0, universal_1.parseUniversal)(content, file);
11
11
  const ext = path_1.default.extname(file);
@@ -5,6 +5,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parseUniversal = parseUniversal;
7
7
  const path_1 = __importDefault(require("path"));
8
+ /**
9
+ * Strip comments (HTML + JS)
10
+ */
11
+ function stripComments(input) {
12
+ return input
13
+ .replace(/<!--[\s\S]*?-->/g, "")
14
+ .replace(/\/\*[\s\S]*?\*\//g, "")
15
+ .replace(/\/\/.*$/gm, "");
16
+ }
17
+ /**
18
+ * Extract JS expressions safely
19
+ */
8
20
  function extractJS(input) {
9
21
  const result = [];
10
22
  let i = 0;
@@ -29,7 +41,7 @@ function extractJS(input) {
29
41
  i++;
30
42
  continue;
31
43
  }
32
- // { ... }
44
+ // { ... }
33
45
  if (ch === "{") {
34
46
  let depth = 1;
35
47
  let j = i + 1;
@@ -60,13 +72,18 @@ function extractJS(input) {
60
72
  }
61
73
  if (depth === 0) {
62
74
  const expr = input.slice(i + 1, j - 1).trim();
63
- if (expr)
75
+ if (expr &&
76
+ (expr.includes("(") ||
77
+ expr.includes("=") ||
78
+ expr.includes(".") ||
79
+ expr.includes("track"))) {
64
80
  result.push(expr);
81
+ }
65
82
  i = j;
66
83
  continue;
67
84
  }
68
85
  }
69
- // attr="..."
86
+ // attr="..."
70
87
  if (ch === "=" && (input[i + 1] === '"' || input[i + 1] === "'")) {
71
88
  const quote = input[i + 1];
72
89
  let j = i + 2;
@@ -83,14 +100,18 @@ function extractJS(input) {
83
100
  val += c;
84
101
  j++;
85
102
  }
86
- if (val.trim())
103
+ if (val.trim() &&
104
+ (val.includes("(") ||
105
+ val.includes("=>") ||
106
+ val.includes("track"))) {
87
107
  result.push(val.trim());
108
+ }
88
109
  i = j + 1;
89
110
  continue;
90
111
  }
91
112
  i++;
92
113
  }
93
- // Vue / Svelte handlers
114
+ // Vue / Svelte / JSX handlers
94
115
  const extra = [
95
116
  /@[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
96
117
  /v-on:[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
@@ -108,21 +129,41 @@ function extractJS(input) {
108
129
  return result;
109
130
  }
110
131
  /**
111
- * - Astro
112
- * - Vue
113
- * - Svelte
114
- * - HTML + JS
115
- * - SSR / mixed files
132
+ * Extract loose JS but less noisy
133
+ */
134
+ function extractLooseJS(input) {
135
+ const result = [];
136
+ const lines = input.split("\n");
137
+ for (const line of lines) {
138
+ const l = line.trim();
139
+ if (!l)
140
+ continue;
141
+ if (l.startsWith("<"))
142
+ continue;
143
+ // strict filter
144
+ if (l.includes("track") ||
145
+ l.includes(".track(") ||
146
+ l.includes("=>") ||
147
+ l.startsWith("function") ||
148
+ l.includes("(")) {
149
+ result.push(l);
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ /**
155
+ * Universal parser
116
156
  */
117
157
  function parseUniversal(content, file) {
118
158
  const parts = [];
119
159
  const deps = [];
120
- // Astro frontmatter
121
- const fm = content.match(/^---([\s\S]*?)---/);
160
+ const clean = stripComments(content);
161
+ // Astro frontmatter
162
+ const fm = clean.match(/^---([\s\S]*?)---/);
122
163
  if (fm?.[1])
123
164
  parts.push(fm[1]);
124
165
  // <script src="">
125
- const scriptSrc = content.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
166
+ const scriptSrc = clean.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
126
167
  for (const s of scriptSrc) {
127
168
  const src = s[1];
128
169
  const resolved = src.startsWith("/")
@@ -131,20 +172,23 @@ function parseUniversal(content, file) {
131
172
  deps.push(resolved);
132
173
  }
133
174
  // inline <script>
134
- const scripts = content.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g);
175
+ const scripts = clean.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/g);
135
176
  for (const m of scripts) {
136
- if (m[1].trim())
137
- parts.push(m[1]);
177
+ const attrs = m[1];
178
+ // skip JSON / data
179
+ if (/type=["']application\/json/.test(attrs))
180
+ continue;
181
+ if (m[2].trim()) {
182
+ parts.push(m[2]);
183
+ }
138
184
  }
139
- // JS <script>
140
- const noScripts = content.replace(/<script[\s\S]*?<\/script>/g, "");
141
- const noHtml = noScripts
142
- .replace(/<[^>]+>/g, "\n")
143
- .replace(/\n+/g, "\n");
144
- if (noHtml.trim())
145
- parts.push(noHtml);
185
+ // remove scripts
186
+ const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
187
+ // loose JS
188
+ const loose = extractLooseJS(noScripts);
189
+ parts.push(...loose);
146
190
  // expressions
147
- const extracted = extractJS(content);
191
+ const extracted = extractJS(clean);
148
192
  for (const e of extracted) {
149
193
  parts.push(e + ";");
150
194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -15,9 +15,24 @@ export function findTrackParamIndex(
15
15
 
16
16
  function visit(node: ts.Node) {
17
17
  if (ts.isCallExpression(node)) {
18
- const name = getCallName(node.expression);
18
+ let isTrackCall = false;
19
19
 
20
- if (/(track|event|capture|send)/i.test(name)) {
20
+ if (ts.isPropertyAccessExpression(node.expression)) {
21
+ const method = node.expression.name.text;
22
+
23
+ if (method === "track") {
24
+ isTrackCall = true;
25
+ }
26
+ }
27
+
28
+ // fallback
29
+ if (ts.isIdentifier(node.expression)) {
30
+ if (node.expression.text === "track") {
31
+ isTrackCall = true;
32
+ }
33
+ }
34
+
35
+ if (isTrackCall) {
21
36
  for (const arg of node.arguments) {
22
37
  if (ts.isIdentifier(arg)) {
23
38
  const idx = paramIndex.get(arg.text);
@@ -26,6 +41,11 @@ export function findTrackParamIndex(
26
41
  return;
27
42
  }
28
43
  }
44
+
45
+ if (ts.isStringLiteral(arg)) {
46
+ result = 0;
47
+ return;
48
+ }
29
49
  }
30
50
  }
31
51
  }
@@ -39,13 +59,3 @@ export function findTrackParamIndex(
39
59
 
40
60
  return result;
41
61
  }
42
-
43
- function getCallName(expr: ts.Expression): string {
44
- if (ts.isIdentifier(expr)) return expr.text;
45
-
46
- if (ts.isPropertyAccessExpression(expr)) {
47
- return expr.name.text;
48
- }
49
-
50
- return "";
51
- }
@@ -12,12 +12,12 @@ export function resolveNodeValue(
12
12
  paramMap?: Map<string, ts.Expression>,
13
13
  seen = new Set<ts.Node>()
14
14
  ): ResolveResult {
15
-
16
15
  if (seen.has(node)) {
17
16
  return { values: [], dynamic: true };
18
17
  }
19
18
  seen.add(node);
20
19
 
20
+ // param mapping
21
21
  if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
22
22
  return resolveNodeValue(
23
23
  paramMap.get(node.text)!,
@@ -27,54 +27,70 @@ export function resolveNodeValue(
27
27
  );
28
28
  }
29
29
 
30
+ // STRING
30
31
  if (ts.isStringLiteral(node)) {
31
32
  return { values: [node.text], dynamic: false };
32
33
  }
33
34
 
35
+ // ARRAY
36
+ if (ts.isArrayLiteralExpression(node)) {
37
+ const values: string[] = [];
38
+
39
+ for (const el of node.elements) {
40
+ const res = resolveNodeValue(el, checker, paramMap, seen);
41
+ if (res.values.length) {
42
+ values.push(...res.values);
43
+ }
44
+ }
45
+
46
+ return { values, dynamic: true };
47
+ }
48
+
49
+ // TEMPLATE
34
50
  if (ts.isTemplateExpression(node)) {
35
- let result = node.head.text;
51
+ let results = [node.head.text];
36
52
 
37
53
  for (const span of node.templateSpans) {
38
- result += "*";
39
- result += span.literal.text;
54
+ const res = resolveNodeValue(span.expression, checker, paramMap, seen);
55
+
56
+ if (res.values.length) {
57
+ results = results.flatMap(r =>
58
+ res.values.map(v => r + v)
59
+ );
60
+ } else {
61
+ results = results.map(r => r + "*");
62
+ }
63
+
64
+ results = results.map(r => r + span.literal.text);
40
65
  }
41
66
 
42
- return { values: [result], dynamic: true };
67
+ return { values: results, dynamic: true };
43
68
  }
44
69
 
70
+ // CONDITIONAL
45
71
  if (ts.isConditionalExpression(node)) {
46
72
  const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
47
73
  const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
48
74
 
49
75
  return {
50
- values: [...new Set([...(a.values ?? []), ...(b.values ?? [])])],
76
+ values: [...new Set([...a.values, ...b.values])],
51
77
  dynamic: true,
52
78
  };
53
79
  }
54
80
 
81
+ // IDENTIFIER
55
82
  if (ts.isIdentifier(node)) {
56
83
  let symbol = checker.getSymbolAtLocation(node);
57
84
  if (!symbol) return { values: [], dynamic: true };
58
85
 
59
- for (const decl of symbol.getDeclarations() ?? []) {
60
- if (ts.isVariableDeclaration(decl) && decl.initializer) {
61
- return resolveNodeValue(
62
- decl.initializer,
63
- checker,
64
- paramMap,
65
- seen
66
- );
67
- }
68
- }
69
-
70
- const original = symbol;
71
-
86
+ // unwrap export
72
87
  symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
73
88
 
74
- if (symbol !== original && (symbol.flags & ts.SymbolFlags.Alias)) {
89
+ if (symbol.flags & ts.SymbolFlags.Alias) {
75
90
  symbol = checker.getAliasedSymbol(symbol);
76
91
  }
77
92
 
93
+ // inline variable
78
94
  for (const decl of symbol.getDeclarations() ?? []) {
79
95
  if (ts.isVariableDeclaration(decl) && decl.initializer) {
80
96
  return resolveNodeValue(
@@ -95,14 +111,38 @@ export function resolveNodeValue(
95
111
  }
96
112
  }
97
113
 
114
+ // UNION TYPES
115
+ const type = checker.getTypeAtLocation(node);
116
+
117
+ if (type.isUnion()) {
118
+ const values: string[] = [];
119
+
120
+ for (const t of type.types) {
121
+ if ((t as any).isStringLiteral?.()) {
122
+ values.push((t as any).value);
123
+ }
124
+ }
125
+
126
+ if (values.length) {
127
+ return { values, dynamic: false };
128
+ }
129
+ }
130
+
131
+ // TYPE FALLBACK (as const)
132
+ if ((type as any).isStringLiteral?.()) {
133
+ return { values: [(type as any).value], dynamic: false };
134
+ }
135
+
98
136
  return { values: [], dynamic: true };
99
137
  }
100
138
 
139
+ // PROPERTY ACCESS (obj.a.b)
101
140
  if (ts.isPropertyAccessExpression(node)) {
141
+
102
142
  const symbol = checker.getSymbolAtLocation(node);
103
143
 
104
144
  if (symbol) {
105
- let s = symbol;
145
+ let s = resolveExportedSymbol(symbol, checker) ?? symbol;
106
146
 
107
147
  if (s.flags & ts.SymbolFlags.Alias) {
108
148
  s = checker.getAliasedSymbol(s);
@@ -120,6 +160,23 @@ export function resolveNodeValue(
120
160
  }
121
161
  }
122
162
 
163
+ // constant enum / readonly
164
+ const constantValue = checker.getConstantValue(node as any);
165
+ if (typeof constantValue === "string") {
166
+ return { values: [constantValue], dynamic: false };
167
+ }
168
+
169
+ // type fallback
170
+ const type = checker.getTypeAtLocation(node);
171
+
172
+ if ((type as any).isStringLiteral?.()) {
173
+ return {
174
+ values: [(type as any).value],
175
+ dynamic: false,
176
+ };
177
+ }
178
+
179
+ // object literal resolve
123
180
  if (ts.isIdentifier(node.expression)) {
124
181
  const objSymbol = checker.getSymbolAtLocation(node.expression);
125
182
 
@@ -152,6 +209,40 @@ export function resolveNodeValue(
152
209
  return { values: [], dynamic: true };
153
210
  }
154
211
 
212
+ // ARRAY ACCESS (arr[0])
213
+ if (ts.isElementAccessExpression(node)) {
214
+ const obj = resolveNodeValue(node.expression, checker, paramMap, seen);
215
+
216
+ if (!node.argumentExpression) {
217
+ return { values: obj.values, dynamic: true };
218
+ }
219
+
220
+ const index = resolveNodeValue(
221
+ node.argumentExpression,
222
+ checker,
223
+ paramMap,
224
+ seen
225
+ );
226
+
227
+ // if index known
228
+ if (index.values.length === 1) {
229
+ const i = Number(index.values[0]);
230
+
231
+ if (!Number.isNaN(i) && obj.values[i] !== undefined) {
232
+ return {
233
+ values: [obj.values[i]],
234
+ dynamic: false,
235
+ };
236
+ }
237
+ }
238
+
239
+ // fallback → dynamic
240
+ return obj.values.length
241
+ ? { values: obj.values, dynamic: true }
242
+ : { values: [], dynamic: true };
243
+ }
244
+
245
+ // OBJECT
155
246
  if (ts.isObjectLiteralExpression(node)) {
156
247
  const values: string[] = [];
157
248
 
@@ -160,7 +251,7 @@ export function resolveNodeValue(
160
251
 
161
252
  const name = prop.name.getText();
162
253
 
163
- if (name === "event") {
254
+ if (["event", "name", "type"].includes(name)) {
164
255
  const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
165
256
  values.push(...res.values);
166
257
  }
@@ -169,24 +260,28 @@ export function resolveNodeValue(
169
260
  return { values, dynamic: true };
170
261
  }
171
262
 
263
+ // BINARY ("btn_" + name)
172
264
  if (ts.isBinaryExpression(node)) {
173
265
  if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
174
266
  const left = resolveNodeValue(node.left, checker, paramMap, seen);
175
267
  const right = resolveNodeValue(node.right, checker, paramMap, seen);
176
268
 
177
- if (left && right) {
178
- return {
179
- values: left.values.flatMap(l =>
180
- right.values.length
181
- ? right.values.map(r => l + r)
182
- : [l + "*"]
183
- ),
184
- dynamic: true,
185
- };
269
+ if (!left.values.length) {
270
+ return { values: [], dynamic: true };
186
271
  }
272
+
273
+ return {
274
+ values: left.values.flatMap(l =>
275
+ right.values.length
276
+ ? right.values.map(r => l + r)
277
+ : [l + "*"]
278
+ ),
279
+ dynamic: true,
280
+ };
187
281
  }
188
282
  }
189
283
 
284
+ // CALL → dynamic
190
285
  if (ts.isCallExpression(node)) {
191
286
  return { values: [], dynamic: true };
192
287
  }
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import { parseUniversal } from "./parsers/universal";
2
+ import { parseUniversal } from "./universal";
3
3
 
4
4
  export function processFile(file: string, content: string) {
5
5
  const { code, deps } = parseUniversal(content, file);
@@ -1,6 +1,19 @@
1
1
  import path from "path";
2
- import { ParseResult } from "../../types";
2
+ import { ParseResult } from "../types";
3
3
 
4
+ /**
5
+ * Strip comments (HTML + JS)
6
+ */
7
+ function stripComments(input: string): string {
8
+ return input
9
+ .replace(/<!--[\s\S]*?-->/g, "")
10
+ .replace(/\/\*[\s\S]*?\*\//g, "")
11
+ .replace(/\/\/.*$/gm, "");
12
+ }
13
+
14
+ /**
15
+ * Extract JS expressions safely
16
+ */
4
17
  function extractJS(input: string): string[] {
5
18
  const result: string[] = [];
6
19
 
@@ -18,7 +31,6 @@ function extractJS(input: string): string[] {
18
31
  if (escape) escape = false;
19
32
  else if (ch === "\\") escape = true;
20
33
  else if (ch === inString) inString = null;
21
-
22
34
  i++;
23
35
  continue;
24
36
  }
@@ -29,7 +41,7 @@ function extractJS(input: string): string[] {
29
41
  continue;
30
42
  }
31
43
 
32
- // { ... }
44
+ // { ... }
33
45
  if (ch === "{") {
34
46
  let depth = 1;
35
47
  let j = i + 1;
@@ -62,13 +74,23 @@ function extractJS(input: string): string[] {
62
74
 
63
75
  if (depth === 0) {
64
76
  const expr = input.slice(i + 1, j - 1).trim();
65
- if (expr) result.push(expr);
77
+
78
+ if (
79
+ expr &&
80
+ (expr.includes("(") ||
81
+ expr.includes("=") ||
82
+ expr.includes(".") ||
83
+ expr.includes("track"))
84
+ ) {
85
+ result.push(expr);
86
+ }
87
+
66
88
  i = j;
67
89
  continue;
68
90
  }
69
91
  }
70
92
 
71
- // attr="..."
93
+ // attr="..."
72
94
  if (ch === "=" && (input[i + 1] === '"' || input[i + 1] === "'")) {
73
95
  const quote = input[i + 1];
74
96
  let j = i + 2;
@@ -89,7 +111,14 @@ function extractJS(input: string): string[] {
89
111
  j++;
90
112
  }
91
113
 
92
- if (val.trim()) result.push(val.trim());
114
+ if (
115
+ val.trim() &&
116
+ (val.includes("(") ||
117
+ val.includes("=>") ||
118
+ val.includes("track"))
119
+ ) {
120
+ result.push(val.trim());
121
+ }
93
122
 
94
123
  i = j + 1;
95
124
  continue;
@@ -98,7 +127,7 @@ function extractJS(input: string): string[] {
98
127
  i++;
99
128
  }
100
129
 
101
- // Vue / Svelte handlers
130
+ // Vue / Svelte / JSX handlers
102
131
  const extra = [
103
132
  /@[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
104
133
  /v-on:[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
@@ -118,13 +147,37 @@ function extractJS(input: string): string[] {
118
147
  }
119
148
 
120
149
  /**
121
- * - Astro
122
- * - Vue
123
- * - Svelte
124
- * - HTML + JS
125
- * - SSR / mixed files
150
+ * Extract loose JS but less noisy
126
151
  */
152
+ function extractLooseJS(input: string): string[] {
153
+ const result: string[] = [];
154
+
155
+ const lines = input.split("\n");
156
+
157
+ for (const line of lines) {
158
+ const l = line.trim();
159
+
160
+ if (!l) continue;
161
+ if (l.startsWith("<")) continue;
127
162
 
163
+ // strict filter
164
+ if (
165
+ l.includes("track") ||
166
+ l.includes(".track(") ||
167
+ l.includes("=>") ||
168
+ l.startsWith("function") ||
169
+ l.includes("(")
170
+ ) {
171
+ result.push(l);
172
+ }
173
+ }
174
+
175
+ return result;
176
+ }
177
+
178
+ /**
179
+ * Universal parser
180
+ */
128
181
  export function parseUniversal(
129
182
  content: string,
130
183
  file: string
@@ -132,12 +185,14 @@ export function parseUniversal(
132
185
  const parts: string[] = [];
133
186
  const deps: string[] = [];
134
187
 
135
- // Astro frontmatter
136
- const fm = content.match(/^---([\s\S]*?)---/);
188
+ const clean = stripComments(content);
189
+
190
+ // Astro frontmatter
191
+ const fm = clean.match(/^---([\s\S]*?)---/);
137
192
  if (fm?.[1]) parts.push(fm[1]);
138
193
 
139
194
  // <script src="">
140
- const scriptSrc = content.matchAll(
195
+ const scriptSrc = clean.matchAll(
141
196
  /<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g
142
197
  );
143
198
 
@@ -152,29 +207,33 @@ export function parseUniversal(
152
207
  }
153
208
 
154
209
  // inline <script>
155
- const scripts = content.matchAll(
156
- /<script\b[^>]*>([\s\S]*?)<\/script>/g
210
+ const scripts = clean.matchAll(
211
+ /<script\b([^>]*)>([\s\S]*?)<\/script>/g
157
212
  );
158
213
 
159
214
  for (const m of scripts) {
160
- if (m[1].trim()) parts.push(m[1]);
215
+ const attrs = m[1];
216
+
217
+ // skip JSON / data
218
+ if (/type=["']application\/json/.test(attrs)) continue;
219
+
220
+ if (m[2].trim()) {
221
+ parts.push(m[2]);
222
+ }
161
223
  }
162
224
 
163
- // JS <script>
164
- const noScripts = content.replace(
225
+ // remove scripts
226
+ const noScripts = clean.replace(
165
227
  /<script[\s\S]*?<\/script>/g,
166
228
  ""
167
229
  );
168
230
 
169
- const noHtml = noScripts
170
- .replace(/<[^>]+>/g, "\n")
171
- .replace(/\n+/g, "\n");
172
-
173
- if (noHtml.trim()) parts.push(noHtml);
231
+ // loose JS
232
+ const loose = extractLooseJS(noScripts);
233
+ parts.push(...loose);
174
234
 
175
235
  // expressions
176
- const extracted = extractJS(content);
177
-
236
+ const extracted = extractJS(clean);
178
237
  for (const e of extracted) {
179
238
  parts.push(e + ";");
180
239
  }
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.detectParser = detectParser;
4
- const universal_1 = require("./universal");
5
- function detectParser() {
6
- return universal_1.parseUniversal;
7
- }
@@ -1,21 +0,0 @@
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.resolveImport = resolveImport;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
- function resolveImport(from, src) {
10
- if (!src.startsWith("."))
11
- return null;
12
- const base = path_1.default.resolve(path_1.default.dirname(from), src);
13
- const exts = [".ts", ".tsx", ".js", ".jsx"];
14
- for (const ext of exts) {
15
- if (fs_1.default.existsSync(base + ext))
16
- return base + ext;
17
- }
18
- if (fs_1.default.existsSync(base))
19
- return base;
20
- return null;
21
- }
@@ -1,6 +0,0 @@
1
- import { Parser } from "../../types";
2
- import { parseUniversal } from "./universal";
3
-
4
- export function detectParser(): Parser {
5
- return parseUniversal;
6
- }
@@ -1,18 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- export function resolveImport(from: string, src: string): string | null {
5
- if (!src.startsWith(".")) return null;
6
-
7
- const base = path.resolve(path.dirname(from), src);
8
-
9
- const exts = [".ts", ".tsx", ".js", ".jsx"];
10
-
11
- for (const ext of exts) {
12
- if (fs.existsSync(base + ext)) return base + ext;
13
- }
14
-
15
- if (fs.existsSync(base)) return base;
16
-
17
- return null;
18
- }