@eventra_dev/eventra-cli 0.1.24 → 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.
@@ -1,10 +1,12 @@
1
1
  import fg from "fast-glob";
2
2
  import fs from "fs/promises";
3
3
  import chalk from "chalk";
4
+ import path from "path";
4
5
 
5
6
  import { loadConfig, saveConfig } from "../utils/config";
6
7
  import { EventraEngine } from "../engine/engine";
7
8
  import { processFile } from "../utils/processFile";
9
+ import { getVirtualFile } from "../utils/getVirtualFile";
8
10
 
9
11
  export async function sync() {
10
12
  const config = await loadConfig();
@@ -14,6 +16,7 @@ export async function sync() {
14
16
 
15
17
  const files = await fg(config.sync.include, {
16
18
  ignore: config.sync.exclude,
19
+ absolute: true,
17
20
  });
18
21
 
19
22
  if (!files.length) {
@@ -23,76 +26,86 @@ export async function sync() {
23
26
 
24
27
  const engine = new EventraEngine(process.cwd());
25
28
 
26
- const detectedFn = new Set<string>();
27
- const detectedCmp = new Map<string, string>();
29
+ const toScan = new Set<string>();
28
30
 
29
- const fileCache: { file: string; content: string }[] = [];
31
+ const cache = new Map<
32
+ string,
33
+ { content: string; deps: string[] }
34
+ >();
30
35
 
36
+ // COLLECT + CACHE
31
37
  for (const file of files) {
32
38
  try {
33
39
  const raw = await fs.readFile(file, "utf-8");
34
40
 
35
- const { content, virtualFile, deps } = processFile(file, raw);
41
+ if (raw.length > 2_000_000) continue;
36
42
 
37
- engine["ts"].updateFile(virtualFile, content);
38
- fileCache.push({ file: virtualFile, content });
43
+ const parsed = processFile(file, raw);
39
44
 
40
- // deps
41
- for (const dep of deps) {
42
- try {
43
- const depRaw = await fs.readFile(dep, "utf-8");
45
+ cache.set(file, parsed);
46
+ toScan.add(file);
44
47
 
45
- const {
46
- content: depContent,
47
- virtualFile: depVirtual,
48
- } = processFile(dep, depRaw);
48
+ for (const dep of parsed.deps) {
49
+ toScan.add(path.resolve(dep));
50
+ }
49
51
 
50
- engine["ts"].updateFile(depVirtual, depContent);
51
- fileCache.push({ file: depVirtual, content: depContent });
52
+ } catch {}
53
+ }
52
54
 
53
- } catch {}
55
+ // PRELOAD
56
+ for (const file of toScan) {
57
+ try {
58
+ let parsed = cache.get(file);
59
+
60
+ if (!parsed) {
61
+ const raw = await fs.readFile(file, "utf-8");
62
+ parsed = processFile(file, raw);
63
+ cache.set(file, parsed);
54
64
  }
55
65
 
66
+ const virtual = getVirtualFile(file);
67
+ engine.preloadFile(virtual, parsed.content);
68
+
56
69
  } catch {}
57
70
  }
58
71
 
59
- for (const { file, content } of fileCache) {
72
+ // SCAN
73
+ const detectedFn = new Set<string>();
74
+ const detectedCmp = new Map<string, string>();
75
+
76
+ for (const file of toScan) {
60
77
  try {
61
- const res = engine.scanFile(file, content, config);
78
+ const parsed = cache.get(file);
79
+ if (!parsed) continue;
62
80
 
63
- for (const w of res.detectedFunctionWrappers) {
64
- detectedFn.add(w);
65
- }
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)
87
+ );
66
88
 
67
89
  for (const [k, v] of res.detectedComponentWrappers) {
68
90
  detectedCmp.set(k, v);
69
91
  }
70
92
 
71
- } catch (err) {
72
- console.log(chalk.yellow(`Skipped: ${file}`));
73
-
74
- if (err instanceof Error) {
75
- console.log(chalk.gray(err.message));
76
- }
93
+ } catch {
94
+ console.log(chalk.gray(`skip: ${file}`));
77
95
  }
78
96
  }
79
97
 
80
- // EVENTS
98
+ // RESULT
81
99
  const events = engine.getAllEvents().sort();
82
100
 
83
- const mergedEvents = new Set(config.events ?? []);
84
- events.forEach(e => mergedEvents.add(e));
101
+ config.events = events;
85
102
 
86
- config.events = [...mergedEvents].sort();
87
-
88
- // FUNCTION WRAPPERS
89
103
  config.functionWrappers = mergeUnique(
90
104
  config.functionWrappers ?? [],
91
105
  [...detectedFn].map(name => ({ name })),
92
106
  w => w.name
93
107
  );
94
108
 
95
- // COMPONENT WRAPPERS
96
109
  config.wrappers = mergeUnique(
97
110
  config.wrappers ?? [],
98
111
  [...detectedCmp.entries()].map(([name, prop]) => ({
@@ -105,18 +118,6 @@ export async function sync() {
105
118
  await saveConfig(config);
106
119
 
107
120
  console.log(chalk.green(`Found ${events.length} events`));
108
-
109
- if (detectedFn.size) {
110
- console.log(chalk.blue("\nDetected function wrappers:"));
111
- detectedFn.forEach(w => console.log(chalk.gray(`• ${w}`)));
112
- }
113
-
114
- if (detectedCmp.size) {
115
- console.log(chalk.blue("\nDetected component wrappers:"));
116
- detectedCmp.forEach((prop, name) =>
117
- console.log(chalk.gray(`• ${name} (${prop})`))
118
- );
119
- }
120
121
  }
121
122
 
122
123
  // HELPERS
@@ -7,17 +7,15 @@ import path from "path";
7
7
  import { loadConfig, saveConfig } from "../utils/config";
8
8
  import { EventraEngine } from "../engine/engine";
9
9
  import { processFile } from "../utils/processFile";
10
+ import { getVirtualFile } from "../utils/getVirtualFile";
10
11
 
11
12
  type QueueItem = {
12
13
  real: string;
13
14
  virtual: string;
14
15
  };
15
16
 
16
- // stabile virtual mapping
17
- function getVirtualFile(file: string) {
18
- return /\.(vue|svelte|astro|html)$/i.test(file)
19
- ? file + ".tsx"
20
- : file;
17
+ function normalize(file: string) {
18
+ return path.resolve(file).replace(/\\/g, "/");
21
19
  }
22
20
 
23
21
  export async function watch() {
@@ -28,26 +26,34 @@ export async function watch() {
28
26
 
29
27
  const files = await fg(config.sync.include, {
30
28
  ignore: config.sync.exclude,
29
+ absolute: true,
31
30
  });
32
31
 
33
32
  const engine = new EventraEngine(process.cwd());
34
-
35
33
  const watchedDeps = new Set<string>();
36
34
 
37
- // INITIAL LOAD
35
+ const cache = new Map<
36
+ string,
37
+ { content: string; deps: string[] }
38
+ >();
39
+
40
+ const toScan = new Set<string>();
41
+
42
+ // COLLECT + CACHE
38
43
  for (const file of files) {
39
44
  try {
40
- const abs = path.resolve(file);
45
+ const abs = normalize(file);
41
46
  const raw = await fs.readFile(abs, "utf-8");
42
47
 
43
- const { content, deps } = processFile(abs, raw);
44
- const virtual = getVirtualFile(abs);
48
+ if (raw.length > 2_000_000) continue;
49
+
50
+ const parsed = processFile(abs, raw);
45
51
 
46
- engine.scanFile(virtual, content, config);
52
+ cache.set(abs, parsed);
53
+ toScan.add(abs);
47
54
 
48
- // deps
49
- for (const dep of deps) {
50
- watchedDeps.add(path.resolve(dep));
55
+ for (const dep of parsed.deps) {
56
+ toScan.add(normalize(dep));
51
57
  }
52
58
 
53
59
  } catch {
@@ -55,6 +61,44 @@ export async function watch() {
55
61
  }
56
62
  }
57
63
 
64
+ // PRELOAD
65
+ for (const file of toScan) {
66
+ try {
67
+ let parsed = cache.get(file);
68
+
69
+ if (!parsed) {
70
+ const raw = await fs.readFile(file, "utf-8");
71
+ parsed = processFile(file, raw);
72
+ cache.set(file, parsed);
73
+ }
74
+
75
+ const virtual = getVirtualFile(file);
76
+ engine.preloadFile?.(virtual, parsed.content);
77
+
78
+ } catch {}
79
+ }
80
+
81
+ // INITIAL SCAN
82
+ for (const file of toScan) {
83
+ try {
84
+ let parsed = cache.get(file);
85
+
86
+ if (!parsed) {
87
+ const raw = await fs.readFile(file, "utf-8");
88
+ parsed = processFile(file, raw);
89
+ }
90
+
91
+ const virtual = getVirtualFile(file);
92
+
93
+ engine.scanFile(virtual, parsed.content, config);
94
+
95
+ for (const dep of parsed.deps) {
96
+ watchedDeps.add(normalize(dep));
97
+ }
98
+
99
+ } catch {}
100
+ }
101
+
58
102
  config.events = engine.getAllEvents().sort();
59
103
  await saveConfig(config);
60
104
 
@@ -64,15 +108,11 @@ export async function watch() {
64
108
 
65
109
  // QUEUE
66
110
  let locked = false;
67
- let queued = new Map<string, QueueItem>();
111
+ const queued = new Map<string, QueueItem>();
68
112
  let timer: NodeJS.Timeout | null = null;
69
113
 
70
114
  const run = async () => {
71
- if (locked) {
72
- timer = setTimeout(run, 100);
73
- return;
74
- }
75
-
115
+ if (locked) return;
76
116
  locked = true;
77
117
 
78
118
  const batch = [...queued.values()];
@@ -82,26 +122,47 @@ export async function watch() {
82
122
  try {
83
123
  const raw = await fs.readFile(item.real, "utf-8");
84
124
 
85
- const { content, deps } = processFile(item.real, raw);
125
+ if (raw.length > 2_000_000) continue;
126
+
127
+ const parsed = processFile(item.real, raw);
128
+ cache.set(item.real, parsed);
86
129
 
87
- engine.updateFile(item.virtual, content, config);
130
+ engine.updateFile(item.virtual, parsed.content, config);
88
131
 
89
- // DEPS
90
- for (const dep of deps) {
91
- const absDep = path.resolve(dep);
132
+ // DEPENDENCY DIFF
133
+ const nextDeps = new Set<string>();
92
134
 
93
- if (!watchedDeps.has(absDep)) {
94
- watcher.add(absDep);
95
- watchedDeps.add(absDep);
135
+ for (const dep of parsed.deps) {
136
+ nextDeps.add(normalize(dep));
137
+ }
138
+
139
+ // add new deps
140
+ for (const dep of nextDeps) {
141
+ if (!watchedDeps.has(dep)) {
142
+ watcher.add(dep);
143
+ watchedDeps.add(dep);
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);
96
152
  }
153
+ }
97
154
 
155
+ // preload deps
156
+ for (const dep of nextDeps) {
98
157
  try {
99
- const depRaw = await fs.readFile(absDep, "utf-8");
100
- const { content: depContent } = processFile(absDep, depRaw);
158
+ const raw = await fs.readFile(dep, "utf-8");
159
+ const parsedDep = processFile(dep, raw);
101
160
 
102
- const depVirtual = getVirtualFile(absDep);
161
+ cache.set(dep, parsedDep);
103
162
 
104
- engine.updateFile(depVirtual, depContent, config);
163
+ const virtual = getVirtualFile(dep);
164
+
165
+ engine.updateFile(virtual, parsedDep.content, config);
105
166
  } catch {}
106
167
  }
107
168
 
@@ -110,7 +171,7 @@ export async function watch() {
110
171
  }
111
172
  }
112
173
 
113
- // EVENTS DIFF
174
+ // DIFF EVENTS
114
175
  const next = engine.getAllEvents();
115
176
 
116
177
  const added = next.filter(e => !config.events.includes(e));
@@ -133,26 +194,29 @@ export async function watch() {
133
194
 
134
195
  // SCHEDULER
135
196
  const schedule = (file: string) => {
136
- const abs = path.resolve(file);
197
+ const abs = normalize(file);
137
198
  const virtual = getVirtualFile(abs);
138
199
 
200
+ console.log(chalk.gray(`change: ${file}`));
201
+
139
202
  queued.set(abs, { real: abs, virtual });
140
203
 
141
204
  if (timer) clearTimeout(timer);
142
- timer = setTimeout(run, 300);
205
+ timer = setTimeout(run, 200);
143
206
  };
144
207
 
145
208
  // WATCHER
146
209
  const watcher = chokidar.watch(config.sync.include, {
147
210
  ignored: config.sync.exclude,
148
211
  ignoreInitial: true,
212
+ persistent: true,
149
213
  });
150
214
 
151
215
  watcher
152
216
  .on("add", schedule)
153
217
  .on("change", schedule)
154
218
  .on("unlink", async (file) => {
155
- const abs = path.resolve(file);
219
+ const abs = normalize(file);
156
220
 
157
221
  console.log(chalk.gray(`remove: ${file}`));
158
222
 
@@ -160,6 +224,7 @@ export async function watch() {
160
224
 
161
225
  engine.removeFile(virtual, config);
162
226
 
227
+ watcher.unwatch(abs);
163
228
  watchedDeps.delete(abs);
164
229
 
165
230
  const next = engine.getAllEvents();
@@ -170,9 +235,12 @@ export async function watch() {
170
235
  console.log(
171
236
  chalk.gray(`Total: ${config.events.length} events\n`)
172
237
  );
238
+ })
239
+ .on("error", (err) => {
240
+ console.error(chalk.red("Watcher error:"), err);
173
241
  });
174
242
 
175
- // WATCH DEPS
243
+ // WATCH DEPS INIT
176
244
  for (const dep of watchedDeps) {
177
245
  watcher.add(dep);
178
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
  }