@eventra_dev/eventra-cli 0.1.25 → 0.1.26

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,7 +15,7 @@ function stripComments(input) {
15
15
  .replace(/\/\/.*$/gm, "");
16
16
  }
17
17
  /**
18
- * Extract JS expressions safely
18
+ * Extract JS expressions
19
19
  */
20
20
  function extractJS(input) {
21
21
  const result = [];
@@ -25,7 +25,6 @@ function extractJS(input) {
25
25
  let escape = false;
26
26
  while (i < len) {
27
27
  const ch = input[i];
28
- // STRING
29
28
  if (inString) {
30
29
  if (escape)
31
30
  escape = false;
@@ -41,95 +40,31 @@ function extractJS(input) {
41
40
  i++;
42
41
  continue;
43
42
  }
44
- // { ... }
45
43
  if (ch === "{") {
46
44
  let depth = 1;
47
45
  let j = i + 1;
48
- let innerStr = null;
49
- let innerEsc = false;
50
46
  while (j < len && depth > 0) {
51
- const c = input[j];
52
- if (innerStr) {
53
- if (innerEsc)
54
- innerEsc = false;
55
- else if (c === "\\")
56
- innerEsc = true;
57
- else if (c === innerStr)
58
- innerStr = null;
59
- j++;
60
- continue;
61
- }
62
- if (c === '"' || c === "'" || c === "`") {
63
- innerStr = c;
64
- j++;
65
- continue;
66
- }
67
- if (c === "{")
47
+ if (input[j] === "{")
68
48
  depth++;
69
- else if (c === "}")
49
+ else if (input[j] === "}")
70
50
  depth--;
71
51
  j++;
72
52
  }
73
53
  if (depth === 0) {
74
54
  const expr = input.slice(i + 1, j - 1).trim();
75
- if (expr &&
76
- (expr.includes("(") ||
77
- expr.includes("=") ||
78
- expr.includes(".") ||
79
- expr.includes("track"))) {
55
+ if (expr && /[a-zA-Z0-9_$]\s*\(/.test(expr)) {
80
56
  result.push(expr);
81
57
  }
82
58
  i = j;
83
59
  continue;
84
60
  }
85
61
  }
86
- // attr="..."
87
- if (ch === "=" && (input[i + 1] === '"' || input[i + 1] === "'")) {
88
- const quote = input[i + 1];
89
- let j = i + 2;
90
- let val = "";
91
- while (j < len) {
92
- const c = input[j];
93
- if (c === "\\" && input[j + 1]) {
94
- val += c + input[j + 1];
95
- j += 2;
96
- continue;
97
- }
98
- if (c === quote)
99
- break;
100
- val += c;
101
- j++;
102
- }
103
- if (val.trim() &&
104
- (val.includes("(") ||
105
- val.includes("=>") ||
106
- val.includes("track"))) {
107
- result.push(val.trim());
108
- }
109
- i = j + 1;
110
- continue;
111
- }
112
62
  i++;
113
63
  }
114
- // Vue / Svelte / JSX handlers
115
- const extra = [
116
- /@[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
117
- /v-on:[\w-]+\s*=\s*["']([\s\S]*?)["']/g,
118
- /on:[\w-]+\s*=\s*{([\s\S]*?)}/g,
119
- /on\w+\s*=\s*{([\s\S]*?)}/g,
120
- ];
121
- for (const r of extra) {
122
- const m = input.matchAll(r);
123
- for (const x of m) {
124
- const v = x[1]?.trim();
125
- if (v)
126
- result.push(v);
127
- }
128
- }
129
64
  return result;
130
65
  }
131
66
  /**
132
- * Extract loose JS but less noisy
67
+ * Loose JS extractor
133
68
  */
134
69
  function extractLooseJS(input) {
135
70
  const result = [];
@@ -140,28 +75,31 @@ function extractLooseJS(input) {
140
75
  continue;
141
76
  if (l.startsWith("<"))
142
77
  continue;
143
- // strict filter
144
- if (l.includes("track") ||
145
- l.includes(".track(") ||
146
- l.includes("=>") ||
147
- l.startsWith("function") ||
148
- l.includes("(")) {
78
+ if (/[a-zA-Z0-9_$]\s*\(/.test(l)) {
149
79
  result.push(l);
150
80
  }
151
81
  }
152
82
  return result;
153
83
  }
154
84
  /**
155
- * Universal parser
85
+ * UNIVERSAL PARSER
86
+ * Support:
87
+ * - React / JSX
88
+ * - Vue
89
+ * - Svelte
90
+ * - Astro
91
+ * - HTML + JS
92
+ * - SSR
156
93
  */
157
94
  function parseUniversal(content, file) {
158
95
  const parts = [];
159
96
  const deps = [];
160
97
  const clean = stripComments(content);
161
- // Astro frontmatter
98
+ // ASTRO FRONTMATTER
162
99
  const fm = clean.match(/^---([\s\S]*?)---/);
163
- if (fm?.[1])
100
+ if (fm?.[1]) {
164
101
  parts.push(fm[1]);
102
+ }
165
103
  // <script src="">
166
104
  const scriptSrc = clean.matchAll(/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g);
167
105
  for (const s of scriptSrc) {
@@ -171,29 +109,29 @@ function parseUniversal(content, file) {
171
109
  : path_1.default.resolve(path_1.default.dirname(file), src);
172
110
  deps.push(resolved);
173
111
  }
174
- // inline <script>
112
+ // INLINE <script>
175
113
  const scripts = clean.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/g);
176
114
  for (const m of scripts) {
177
115
  const attrs = m[1];
178
- // skip JSON / data
116
+ // skip JSON
179
117
  if (/type=["']application\/json/.test(attrs))
180
118
  continue;
181
119
  if (m[2].trim()) {
182
120
  parts.push(m[2]);
183
121
  }
184
122
  }
185
- // remove scripts
123
+ // REMOVE SCRIPTS → TEMPLATE PART
186
124
  const noScripts = clean.replace(/<script[\s\S]*?<\/script>/g, "");
187
- // loose JS
188
- const loose = extractLooseJS(noScripts);
189
- parts.push(...loose);
190
- // expressions
125
+ // LOOSE JS
126
+ parts.push(...extractLooseJS(noScripts));
127
+ // EXPRESSIONS
191
128
  const extracted = extractJS(clean);
192
129
  for (const e of extracted) {
193
130
  parts.push(e + ";");
194
131
  }
132
+ // RESULT
195
133
  return {
196
134
  code: [...new Set(parts)].join("\n"),
197
- deps,
135
+ deps: [...new Set(deps.filter(Boolean))],
198
136
  };
199
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -14,6 +14,10 @@ type QueueItem = {
14
14
  virtual: string;
15
15
  };
16
16
 
17
+ function normalize(file: string) {
18
+ return path.resolve(file).replace(/\\/g, "/");
19
+ }
20
+
17
21
  export async function watch() {
18
22
  const config = await loadConfig();
19
23
  if (!config) return;
@@ -27,6 +31,7 @@ export async function watch() {
27
31
 
28
32
  const engine = new EventraEngine(process.cwd());
29
33
  const watchedDeps = new Set<string>();
34
+
30
35
  const cache = new Map<
31
36
  string,
32
37
  { content: string; deps: string[] }
@@ -37,18 +42,18 @@ export async function watch() {
37
42
  // COLLECT + CACHE
38
43
  for (const file of files) {
39
44
  try {
40
- const raw = await fs.readFile(file, "utf-8");
45
+ const abs = normalize(file);
46
+ const raw = await fs.readFile(abs, "utf-8");
41
47
 
42
- if (raw.length > 2_000_000) continue; // safeguard
48
+ if (raw.length > 2_000_000) continue;
43
49
 
44
- const parsed = processFile(file, raw);
50
+ const parsed = processFile(abs, raw);
45
51
 
46
- cache.set(file, parsed);
47
- toScan.add(file);
52
+ cache.set(abs, parsed);
53
+ toScan.add(abs);
48
54
 
49
55
  for (const dep of parsed.deps) {
50
- const absDep = path.resolve(dep);
51
- toScan.add(absDep);
56
+ toScan.add(normalize(dep));
52
57
  }
53
58
 
54
59
  } catch {
@@ -56,7 +61,7 @@ export async function watch() {
56
61
  }
57
62
  }
58
63
 
59
- // PRELOAD (TS PROGRAM)
64
+ // PRELOAD
60
65
  for (const file of toScan) {
61
66
  try {
62
67
  let parsed = cache.get(file);
@@ -64,10 +69,10 @@ export async function watch() {
64
69
  if (!parsed) {
65
70
  const raw = await fs.readFile(file, "utf-8");
66
71
  parsed = processFile(file, raw);
72
+ cache.set(file, parsed);
67
73
  }
68
74
 
69
75
  const virtual = getVirtualFile(file);
70
-
71
76
  engine.preloadFile?.(virtual, parsed.content);
72
77
 
73
78
  } catch {}
@@ -88,7 +93,7 @@ export async function watch() {
88
93
  engine.scanFile(virtual, parsed.content, config);
89
94
 
90
95
  for (const dep of parsed.deps) {
91
- watchedDeps.add(path.resolve(dep));
96
+ watchedDeps.add(normalize(dep));
92
97
  }
93
98
 
94
99
  } catch {}
@@ -120,29 +125,44 @@ export async function watch() {
120
125
  if (raw.length > 2_000_000) continue;
121
126
 
122
127
  const parsed = processFile(item.real, raw);
123
-
124
128
  cache.set(item.real, parsed);
125
129
 
126
130
  engine.updateFile(item.virtual, parsed.content, config);
127
131
 
128
- // DEPS
132
+ // DEPENDENCY DIFF
133
+ const nextDeps = new Set<string>();
134
+
129
135
  for (const dep of parsed.deps) {
130
- const absDep = path.resolve(dep);
136
+ nextDeps.add(normalize(dep));
137
+ }
131
138
 
132
- if (!watchedDeps.has(absDep)) {
133
- watcher.add(absDep);
134
- watchedDeps.add(absDep);
139
+ // add new deps
140
+ for (const dep of nextDeps) {
141
+ if (!watchedDeps.has(dep)) {
142
+ watcher.add(dep);
143
+ watchedDeps.add(dep);
135
144
  }
145
+ }
146
+
147
+ // remove old deps
148
+ for (const dep of [...watchedDeps]) {
149
+ if (!nextDeps.has(dep)) {
150
+ watcher.unwatch(dep);
151
+ watchedDeps.delete(dep);
152
+ }
153
+ }
136
154
 
155
+ // preload deps
156
+ for (const dep of nextDeps) {
137
157
  try {
138
- const depRaw = await fs.readFile(absDep, "utf-8");
139
- const depParsed = processFile(absDep, depRaw);
158
+ const raw = await fs.readFile(dep, "utf-8");
159
+ const parsedDep = processFile(dep, raw);
140
160
 
141
- cache.set(absDep, depParsed);
161
+ cache.set(dep, parsedDep);
142
162
 
143
- const depVirtual = getVirtualFile(absDep);
163
+ const virtual = getVirtualFile(dep);
144
164
 
145
- engine.updateFile(depVirtual, depParsed.content, config);
165
+ engine.updateFile(virtual, parsedDep.content, config);
146
166
  } catch {}
147
167
  }
148
168
 
@@ -174,7 +194,7 @@ export async function watch() {
174
194
 
175
195
  // SCHEDULER
176
196
  const schedule = (file: string) => {
177
- const abs = path.resolve(file);
197
+ const abs = normalize(file);
178
198
  const virtual = getVirtualFile(abs);
179
199
 
180
200
  console.log(chalk.gray(`change: ${file}`));
@@ -182,7 +202,7 @@ export async function watch() {
182
202
  queued.set(abs, { real: abs, virtual });
183
203
 
184
204
  if (timer) clearTimeout(timer);
185
- timer = setTimeout(run, 250);
205
+ timer = setTimeout(run, 200);
186
206
  };
187
207
 
188
208
  // WATCHER
@@ -196,7 +216,7 @@ export async function watch() {
196
216
  .on("add", schedule)
197
217
  .on("change", schedule)
198
218
  .on("unlink", async (file) => {
199
- const abs = path.resolve(file);
219
+ const abs = normalize(file);
200
220
 
201
221
  console.log(chalk.gray(`remove: ${file}`));
202
222
 
@@ -220,7 +240,7 @@ export async function watch() {
220
240
  console.error(chalk.red("Watcher error:"), err);
221
241
  });
222
242
 
223
- // WATCH DEPS
243
+ // WATCH DEPS INIT
224
244
  for (const dep of watchedDeps) {
225
245
  watcher.add(dep);
226
246
  }
@@ -29,18 +29,48 @@ function resolveFromSymbol(
29
29
  return resolveFromSymbol(aliased, checker, visited, depth + 1);
30
30
  }
31
31
 
32
+ // re-export support
33
+ if ((symbol as any).exports) {
34
+ for (const exp of (symbol as any).exports.values()) {
35
+ const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
36
+ if (resolved) return resolved;
37
+ }
38
+ }
39
+
32
40
  const decls = symbol.getDeclarations() ?? [];
33
41
 
34
42
  for (const decl of decls) {
43
+ // direct function
35
44
  if (isFunctionLike(decl)) {
36
45
  return decl;
37
46
  }
38
47
 
39
- // MethodSignature
48
+ // VARIABLE → FUNCTION
49
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
50
+ if (isFunctionLike(decl.initializer)) {
51
+ return decl.initializer;
52
+ }
53
+
54
+ const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
55
+ if (innerSymbol && innerSymbol !== symbol) {
56
+ const resolved = resolveFromSymbol(
57
+ innerSymbol,
58
+ checker,
59
+ visited,
60
+ depth + 1
61
+ );
62
+ if (resolved) return resolved;
63
+ }
64
+ }
65
+
66
+ // MethodSignature (interface / type)
40
67
  if (ts.isMethodSignature(decl)) {
41
68
  const parent = decl.parent;
42
69
 
43
- if (ts.isInterfaceDeclaration(parent) || ts.isTypeLiteralNode(parent)) {
70
+ if (
71
+ ts.isInterfaceDeclaration(parent) ||
72
+ ts.isTypeLiteralNode(parent)
73
+ ) {
44
74
  const name = decl.name.getText();
45
75
 
46
76
  const type = checker.getTypeAtLocation(parent);
@@ -49,16 +79,26 @@ function resolveFromSymbol(
49
79
  for (const prop of props) {
50
80
  if (prop.name !== name) continue;
51
81
 
52
- const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
82
+ const resolved = resolveFromSymbol(
83
+ prop,
84
+ checker,
85
+ visited,
86
+ depth + 1
87
+ );
53
88
  if (resolved) return resolved;
54
89
  }
55
90
  }
56
91
  }
57
92
 
58
- // SAFE fallback
93
+ // fallback recursion
59
94
  const s = checker.getSymbolAtLocation(decl);
60
95
  if (s && s !== symbol) {
61
- const resolved = resolveFromSymbol(s, checker, visited, depth + 1);
96
+ const resolved = resolveFromSymbol(
97
+ s,
98
+ checker,
99
+ visited,
100
+ depth + 1
101
+ );
62
102
  if (resolved) return resolved;
63
103
  }
64
104
  }
@@ -70,6 +110,32 @@ export function resolveFunctionFromCall(
70
110
  expr: ts.Expression,
71
111
  checker: ts.TypeChecker
72
112
  ): ts.FunctionLikeDeclaration | null {
113
+
114
+ // tracker.track
115
+ if (ts.isPropertyAccessExpression(expr)) {
116
+ let symbol = checker.getSymbolAtLocation(expr.name);
117
+
118
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
119
+ symbol = checker.getAliasedSymbol(symbol);
120
+ }
121
+
122
+ if (symbol) {
123
+ const resolved = resolveFromSymbol(symbol, checker);
124
+ if (resolved) return resolved;
125
+ }
126
+ }
127
+
128
+ // tracker["track"]
129
+ if (ts.isElementAccessExpression(expr)) {
130
+ const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
131
+
132
+ if (symbol) {
133
+ const resolved = resolveFromSymbol(symbol, checker);
134
+ if (resolved) return resolved;
135
+ }
136
+ }
137
+
138
+ // normal call signatures
73
139
  const type = checker.getTypeAtLocation(expr);
74
140
  const signatures = type.getCallSignatures();
75
141
 
@@ -81,7 +147,6 @@ export function resolveFunctionFromCall(
81
147
  return decl;
82
148
  }
83
149
 
84
- // interface method
85
150
  if (ts.isMethodSignature(decl)) {
86
151
  const symbol = checker.getSymbolAtLocation(decl.name);
87
152
 
@@ -92,5 +157,11 @@ export function resolveFunctionFromCall(
92
157
  }
93
158
  }
94
159
 
160
+ // fallback
161
+ const symbol = checker.getSymbolAtLocation(expr);
162
+ if (symbol) {
163
+ return resolveFromSymbol(symbol, checker);
164
+ }
165
+
95
166
  return null;
96
167
  }
@@ -1,3 +1,6 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+
1
4
  import { TSService } from "./languageService";
2
5
  import { ScanResult, scanSource } from "./scanner";
3
6
  import { DependencyGraph } from "./graph";
@@ -18,8 +21,29 @@ export class EventraEngine {
18
21
  const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
19
22
  this.graph = new DependencyGraph(baseUrl, paths);
20
23
  }
24
+
25
+ private normalize(file: string) {
26
+ return path.resolve(file).replace(/\\/g, "/");
27
+ }
28
+
29
+ private empty(): ScanResult {
30
+ return {
31
+ events: new Set(),
32
+ detectedFunctionWrappers: new Set(),
33
+ detectedComponentWrappers: new Map(),
34
+ };
35
+ }
36
+
37
+ // PRELOAD
21
38
  preloadFile(file: string, content: string) {
39
+ file = this.normalize(file);
40
+
22
41
  this.ts.updateFile(file, content);
42
+
43
+ const source = this.ts.getSourceFile(file);
44
+ if (source) {
45
+ this.graph.update(file, source);
46
+ }
23
47
  }
24
48
 
25
49
  // SCAN FILE
@@ -28,33 +52,26 @@ export class EventraEngine {
28
52
  content: string,
29
53
  config: EventraConfig
30
54
  ): ScanResult {
55
+ file = this.normalize(file);
56
+
31
57
  const h = hash(content);
32
58
 
33
- // skip unchanged
34
59
  if (this.fileHash.get(file) === h) {
35
- return (
36
- this.fileResults.get(file) ?? {
37
- events: new Set(),
38
- detectedFunctionWrappers: new Set(),
39
- detectedComponentWrappers: new Map(),
40
- }
41
- );
60
+ return this.fileResults.get(file) ?? this.empty();
42
61
  }
43
62
 
44
63
  this.fileHash.set(file, h);
64
+
45
65
  this.ts.updateFile(file, content);
46
66
 
47
67
  const source = this.ts.getSourceFile(file);
48
68
  const checker = this.ts.getChecker();
49
69
 
50
70
  if (!source || !checker) {
51
- return {
52
- events: new Set(),
53
- detectedFunctionWrappers: new Set(),
54
- detectedComponentWrappers: new Map(),
55
- };
71
+ return this.empty();
56
72
  }
57
73
 
74
+ // обновляем graph
58
75
  this.graph.update(file, source);
59
76
 
60
77
  const res = scanSource(source, checker, config);
@@ -69,32 +86,46 @@ export class EventraEngine {
69
86
  content: string,
70
87
  config: EventraConfig
71
88
  ) {
89
+ file = this.normalize(file);
90
+
72
91
  const res = this.scanFile(file, content, config);
73
92
 
74
93
  const dependents = this.graph.getAllDependentsDeep(file);
75
94
 
76
95
  for (const dep of dependents) {
77
- if (isExternalFile(dep)) continue;
96
+ const abs = this.normalize(dep);
78
97
 
79
- const depContent = this.ts.getFileContent(dep);
80
- if (!depContent) continue;
98
+ if (isExternalFile(abs)) continue;
99
+
100
+ let depContent = this.ts.getFileContent(abs);
101
+
102
+ if (!depContent) {
103
+ try {
104
+ depContent = fs.readFileSync(abs, "utf-8");
105
+ } catch {
106
+ continue;
107
+ }
108
+ }
81
109
 
82
110
  const h = hash(depContent);
83
111
 
84
- if (this.fileHash.get(dep) === h) continue;
112
+ if (this.fileHash.get(abs) === h) {
113
+ continue;
114
+ }
115
+
116
+ this.fileHash.set(abs, h);
85
117
 
86
- this.fileHash.set(dep, h);
87
- this.ts.updateFile(dep, depContent);
118
+ this.ts.updateFile(abs, depContent);
88
119
 
89
- const source = this.ts.getSourceFile(dep);
120
+ const source = this.ts.getSourceFile(abs);
90
121
  const checker = this.ts.getChecker();
91
122
 
92
123
  if (!source || !checker) continue;
93
124
 
94
- this.graph.update(dep, source);
125
+ this.graph.update(abs, source);
95
126
 
96
127
  const r = scanSource(source, checker, config);
97
- this.fileResults.set(dep, r);
128
+ this.fileResults.set(abs, r);
98
129
  }
99
130
 
100
131
  return res;
@@ -113,31 +144,45 @@ export class EventraEngine {
113
144
 
114
145
  // REMOVE FILE
115
146
  removeFile(file: string, config?: EventraConfig) {
147
+ file = this.normalize(file);
148
+
149
+ const dependents = this.graph.getAllDependentsDeep(file);
150
+
116
151
  this.ts.removeFile(file);
117
152
  this.fileResults.delete(file);
118
153
  this.fileHash.delete(file);
119
154
  this.graph.remove(file);
120
155
 
121
- const dependents = this.graph.getAllDependentsDeep(file);
122
-
123
156
  for (const dep of dependents) {
124
- if (isExternalFile(dep)) continue;
157
+ const abs = this.normalize(dep);
125
158
 
126
- const content = this.ts.getFileContent(dep);
127
- if (!content) continue;
159
+ if (isExternalFile(abs)) continue;
160
+
161
+ let content = this.ts.getFileContent(abs);
162
+
163
+ if (!content) {
164
+ try {
165
+ content = fs.readFileSync(abs, "utf-8");
166
+ } catch {
167
+ continue;
168
+ }
169
+ }
128
170
 
129
171
  const h = hash(content);
130
172
 
131
- if (this.fileHash.get(dep) === h) continue;
173
+ if (this.fileHash.get(abs) === h) continue;
174
+
175
+ this.fileHash.set(abs, h);
132
176
 
133
- this.fileHash.set(dep, h);
134
- this.ts.updateFile(dep, content);
177
+ this.ts.updateFile(abs, content);
135
178
 
136
- const source = this.ts.getSourceFile(dep);
179
+ const source = this.ts.getSourceFile(abs);
137
180
  const checker = this.ts.getChecker();
138
181
 
139
182
  if (!source || !checker) continue;
140
183
 
184
+ this.graph.update(abs, source);
185
+
141
186
  const r = scanSource(
142
187
  source,
143
188
  checker,
@@ -149,7 +194,7 @@ export class EventraEngine {
149
194
  }
150
195
  );
151
196
 
152
- this.fileResults.set(dep, r);
197
+ this.fileResults.set(abs, r);
153
198
  }
154
199
  }
155
200
  }