@eventra_dev/eventra-cli 0.1.34 → 0.2.2

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.
@@ -24,9 +24,9 @@ async function check({ fix = false }) {
24
24
  absolute: true,
25
25
  });
26
26
  const engine = new engine_1.EventraEngine(process.cwd());
27
- const toScan = new Set();
28
27
  const cache = new Map();
29
- // COLLECT + CACHE
28
+ const toScan = new Set();
29
+ // COLLECT
30
30
  for (const file of files) {
31
31
  try {
32
32
  const raw = await promises_1.default.readFile(file, "utf-8");
@@ -35,23 +35,17 @@ async function check({ fix = false }) {
35
35
  const parsed = (0, processFile_1.processFile)(file, raw);
36
36
  cache.set(file, parsed);
37
37
  toScan.add(file);
38
- for (const dep of parsed.deps) {
39
- toScan.add(path_1.default.resolve(dep));
40
- }
38
+ parsed.deps.forEach(dep => toScan.add(path_1.default.resolve(dep)));
41
39
  }
42
40
  catch { }
43
41
  }
44
42
  // PRELOAD
45
43
  for (const file of toScan) {
46
44
  try {
47
- let parsed = cache.get(file);
48
- if (!parsed) {
49
- const raw = await promises_1.default.readFile(file, "utf-8");
50
- parsed = (0, processFile_1.processFile)(file, raw);
51
- cache.set(file, parsed);
52
- }
53
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
54
- engine.preloadFile(virtual, parsed.content);
45
+ const parsed = cache.get(file);
46
+ if (!parsed)
47
+ continue;
48
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
55
49
  }
56
50
  catch { }
57
51
  }
@@ -61,34 +55,24 @@ async function check({ fix = false }) {
61
55
  const parsed = cache.get(file);
62
56
  if (!parsed)
63
57
  continue;
64
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
65
- engine.scanFile(virtual, parsed.content, config);
58
+ engine.scanFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
66
59
  }
67
60
  catch { }
68
61
  }
69
- // DIFF
70
62
  const found = new Set(engine.getAllEvents());
71
63
  const known = new Set(config.events ?? []);
72
- const newEvents = [...found].filter(e => !known.has(e));
64
+ const added = [...found].filter(e => !known.has(e));
73
65
  const removed = [...known].filter(e => !found.has(e));
74
- // FIX MODE
75
66
  if (fix) {
76
67
  config.events = [...found].sort();
77
68
  await (0, config_1.saveConfig)(config);
78
- console.log(chalk_1.default.green(`\nSynced (${config.events.length} events)`));
69
+ console.log(chalk_1.default.green("Synced"));
79
70
  return;
80
71
  }
81
- // CHECK MODE
82
- if (newEvents.length || removed.length) {
83
- if (newEvents.length) {
84
- console.log(chalk_1.default.red("\nNew events:"));
85
- newEvents.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
86
- }
87
- if (removed.length) {
88
- console.log(chalk_1.default.yellow("\nRemoved events:"));
89
- removed.forEach(e => console.log(chalk_1.default.yellow(`- ${e}`)));
90
- }
72
+ if (added.length || removed.length) {
73
+ added.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
74
+ removed.forEach(e => console.log(chalk_1.default.yellow(`- ${e}`)));
91
75
  process.exit(1);
92
76
  }
93
- console.log(chalk_1.default.green("\nAll good"));
77
+ console.log(chalk_1.default.green("All good"));
94
78
  }
@@ -21,14 +21,10 @@ async function sync() {
21
21
  ignore: config.sync.exclude,
22
22
  absolute: true,
23
23
  });
24
- if (!files.length) {
25
- console.log(chalk_1.default.yellow("No files found"));
26
- return;
27
- }
28
24
  const engine = new engine_1.EventraEngine(process.cwd());
29
- const toScan = new Set();
30
25
  const cache = new Map();
31
- // COLLECT + CACHE
26
+ const toScan = new Set();
27
+ // COLLECT
32
28
  for (const file of files) {
33
29
  try {
34
30
  const raw = await promises_1.default.readFile(file, "utf-8");
@@ -37,9 +33,7 @@ async function sync() {
37
33
  const parsed = (0, processFile_1.processFile)(file, raw);
38
34
  cache.set(file, parsed);
39
35
  toScan.add(file);
40
- for (const dep of parsed.deps) {
41
- toScan.add(path_1.default.resolve(dep));
42
- }
36
+ parsed.deps.forEach(dep => toScan.add(path_1.default.resolve(dep)));
43
37
  }
44
38
  catch { }
45
39
  }
@@ -52,8 +46,7 @@ async function sync() {
52
46
  parsed = (0, processFile_1.processFile)(file, raw);
53
47
  cache.set(file, parsed);
54
48
  }
55
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
56
- engine.preloadFile(virtual, parsed.content);
49
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
57
50
  }
58
51
  catch { }
59
52
  }
@@ -65,33 +58,22 @@ async function sync() {
65
58
  const parsed = cache.get(file);
66
59
  if (!parsed)
67
60
  continue;
68
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
69
- const res = engine.scanFile(virtual, parsed.content, config);
61
+ const res = engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
70
62
  res.detectedFunctionWrappers.forEach(w => detectedFn.add(w));
71
- for (const [k, v] of res.detectedComponentWrappers) {
72
- detectedCmp.set(k, v);
73
- }
74
- }
75
- catch {
76
- console.log(chalk_1.default.gray(`skip: ${file}`));
63
+ res.detectedComponentWrappers.forEach((v, k) => detectedCmp.set(k, v));
77
64
  }
65
+ catch { }
78
66
  }
79
67
  // RESULT
80
68
  const events = engine.getAllEvents().sort();
81
69
  config.events = events;
82
70
  config.functionWrappers = mergeUnique(config.functionWrappers ?? [], [...detectedFn].map(name => ({ name })), w => w.name);
83
- config.wrappers = mergeUnique(config.wrappers ?? [], [...detectedCmp.entries()].map(([name, prop]) => ({
84
- name,
85
- prop,
86
- })), w => `${w.name}:${w.prop}`);
71
+ config.wrappers = mergeUnique(config.wrappers ?? [], [...detectedCmp.entries()].map(([name, prop]) => ({ name, prop })), w => `${w.name}:${w.prop}`);
87
72
  await (0, config_1.saveConfig)(config);
88
73
  console.log(chalk_1.default.green(`Found ${events.length} events`));
89
74
  }
90
- // HELPERS
91
75
  function mergeUnique(a, b, key) {
92
76
  const map = new Map();
93
- [...b, ...a].forEach(item => {
94
- map.set(key(item), item);
95
- });
77
+ [...b, ...a].forEach(item => map.set(key(item), item));
96
78
  return [...map.values()];
97
79
  }
@@ -13,180 +13,97 @@ const config_1 = require("../utils/config");
13
13
  const engine_1 = require("../engine/engine");
14
14
  const processFile_1 = require("../utils/processFile");
15
15
  const getVirtualFile_1 = require("../utils/getVirtualFile");
16
- function normalize(file) {
17
- return path_1.default.resolve(file).replace(/\\/g, "/");
18
- }
19
16
  async function watch() {
20
17
  const config = await (0, config_1.loadConfig)();
21
18
  if (!config)
22
19
  return;
23
20
  console.log(chalk_1.default.blue("Watching...\n"));
21
+ const engine = new engine_1.EventraEngine(process.cwd());
22
+ const fileDeps = new Map();
23
+ const cache = new Map();
24
+ const watchedDeps = new Set();
25
+ const normalize = (f) => path_1.default.resolve(f).replace(/\\/g, "/");
26
+ // INITIAL LOAD
24
27
  const files = await (0, fast_glob_1.default)(config.sync.include, {
25
28
  ignore: config.sync.exclude,
26
29
  absolute: true,
27
30
  });
28
- const engine = new engine_1.EventraEngine(process.cwd());
29
- const watchedDeps = new Set();
30
- const cache = new Map();
31
- const toScan = new Set();
32
- // COLLECT + CACHE
33
31
  for (const file of files) {
34
32
  try {
35
33
  const abs = normalize(file);
36
34
  const raw = await promises_1.default.readFile(abs, "utf-8");
37
- if (raw.length > 2_000_000)
38
- continue;
39
35
  const parsed = (0, processFile_1.processFile)(abs, raw);
40
36
  cache.set(abs, parsed);
41
- toScan.add(abs);
42
- for (const dep of parsed.deps) {
43
- toScan.add(normalize(dep));
44
- }
45
- }
46
- catch {
47
- console.log(chalk_1.default.gray(`skip: ${file}`));
48
- }
49
- }
50
- // PRELOAD
51
- for (const file of toScan) {
52
- try {
53
- let parsed = cache.get(file);
54
- if (!parsed) {
55
- const raw = await promises_1.default.readFile(file, "utf-8");
56
- parsed = (0, processFile_1.processFile)(file, raw);
57
- cache.set(file, parsed);
58
- }
59
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
60
- engine.preloadFile?.(virtual, parsed.content);
61
- }
62
- catch { }
63
- }
64
- // INITIAL SCAN
65
- for (const file of toScan) {
66
- try {
67
- let parsed = cache.get(file);
68
- if (!parsed) {
69
- const raw = await promises_1.default.readFile(file, "utf-8");
70
- parsed = (0, processFile_1.processFile)(file, raw);
71
- }
72
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
73
- engine.scanFile(virtual, parsed.content, config);
74
- for (const dep of parsed.deps) {
75
- watchedDeps.add(normalize(dep));
76
- }
37
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content);
38
+ engine.updateFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content, config);
39
+ fileDeps.set(abs, new Set(parsed.deps.map(d => normalize(d))));
77
40
  }
78
41
  catch { }
79
42
  }
80
43
  config.events = engine.getAllEvents().sort();
81
44
  await (0, config_1.saveConfig)(config);
82
- console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
83
- // QUEUE
84
- let locked = false;
85
- const queued = new Map();
45
+ console.log(chalk_1.default.gray(`Initial: ${config.events.length}\n`));
46
+ // WATCHER
47
+ const watcher = chokidar_1.default.watch([...files.map(normalize), ...watchedDeps], { ignoreInitial: true });
48
+ const queue = new Map();
86
49
  let timer = null;
87
50
  const run = async () => {
88
- if (locked)
89
- return;
90
- locked = true;
91
- const batch = [...queued.values()];
92
- queued.clear();
93
- for (const item of batch) {
51
+ const batch = [...queue.keys()];
52
+ queue.clear();
53
+ for (const file of batch) {
94
54
  try {
95
- const raw = await promises_1.default.readFile(item.real, "utf-8");
96
- if (raw.length > 2_000_000)
97
- continue;
98
- const parsed = (0, processFile_1.processFile)(item.real, raw);
99
- cache.set(item.real, parsed);
100
- engine.updateFile(item.virtual, parsed.content, config);
101
- // DEPENDENCY DIFF
102
- const nextDeps = new Set();
103
- for (const dep of parsed.deps) {
104
- nextDeps.add(normalize(dep));
105
- }
55
+ const raw = await promises_1.default.readFile(file, "utf-8");
56
+ const parsed = (0, processFile_1.processFile)(file, raw);
57
+ cache.set(file, parsed);
58
+ engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
59
+ //dependency diff
60
+ const abs = normalize(file);
61
+ const prevDeps = fileDeps.get(abs) ?? new Set();
62
+ const nextDeps = new Set(parsed.deps.map(d => normalize(d)));
106
63
  // add new deps
107
64
  for (const dep of nextDeps) {
108
- if (!watchedDeps.has(dep)) {
65
+ if (!prevDeps.has(dep)) {
109
66
  watcher.add(dep);
110
67
  watchedDeps.add(dep);
111
68
  }
112
69
  }
113
70
  // remove old deps
114
- for (const dep of [...watchedDeps]) {
71
+ for (const dep of prevDeps) {
115
72
  if (!nextDeps.has(dep)) {
116
73
  watcher.unwatch(dep);
117
74
  watchedDeps.delete(dep);
118
75
  }
119
76
  }
120
- // preload deps
121
- for (const dep of nextDeps) {
122
- try {
123
- const raw = await promises_1.default.readFile(dep, "utf-8");
124
- const parsedDep = (0, processFile_1.processFile)(dep, raw);
125
- cache.set(dep, parsedDep);
126
- const virtual = (0, getVirtualFile_1.getVirtualFile)(dep);
127
- engine.updateFile(virtual, parsedDep.content, config);
128
- }
129
- catch { }
130
- }
77
+ fileDeps.set(abs, nextDeps);
131
78
  }
132
79
  catch {
133
- console.log(chalk_1.default.gray(`skip: ${item.real}`));
80
+ console.log(chalk_1.default.gray(`skip: ${file}`));
134
81
  }
135
82
  }
136
- // DIFF EVENTS
137
- const next = engine.getAllEvents();
138
- const added = next.filter(e => !config.events.includes(e));
139
- const removed = config.events.filter(e => !next.includes(e));
140
- if (added.length || removed.length) {
141
- added.forEach(e => console.log(chalk_1.default.green(`+ ${e}`)));
142
- removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
143
- config.events = next.sort();
144
- await (0, config_1.saveConfig)(config);
145
- console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
146
- }
147
- locked = false;
83
+ const next = engine.getAllEvents().sort();
84
+ config.events = next;
85
+ await (0, config_1.saveConfig)(config);
86
+ console.log(chalk_1.default.green(`Updated: ${next.length}`));
148
87
  };
149
- // SCHEDULER
150
88
  const schedule = (file) => {
151
89
  const abs = normalize(file);
152
- const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
153
- console.log(chalk_1.default.gray(`change: ${file}`));
154
- queued.set(abs, { real: abs, virtual });
90
+ queue.set(abs, true);
155
91
  if (timer)
156
92
  clearTimeout(timer);
157
93
  timer = setTimeout(run, 200);
158
94
  };
159
- // WATCHER
160
- const watcher = chokidar_1.default.watch(config.sync.include, {
161
- ignored: config.sync.exclude,
162
- ignoreInitial: true,
163
- persistent: true,
164
- });
165
95
  watcher
166
- .on("add", schedule)
167
96
  .on("change", schedule)
97
+ .on("add", schedule)
168
98
  .on("unlink", async (file) => {
169
99
  const abs = normalize(file);
170
- console.log(chalk_1.default.gray(`remove: ${file}`));
171
- const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
172
- engine.removeFile(virtual, config);
173
- watcher.unwatch(abs);
174
- watchedDeps.delete(abs);
175
- const next = engine.getAllEvents();
176
- config.events = next.sort();
100
+ engine.removeFile((0, getVirtualFile_1.getVirtualFile)(abs), config);
101
+ const next = engine.getAllEvents().sort();
102
+ config.events = next;
177
103
  await (0, config_1.saveConfig)(config);
178
- console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
179
- })
180
- .on("error", (err) => {
181
- console.error(chalk_1.default.red("Watcher error:"), err);
104
+ console.log(chalk_1.default.red(`Removed → ${next.length}`));
182
105
  });
183
- // WATCH DEPS INIT
184
- for (const dep of watchedDeps) {
185
- watcher.add(dep);
186
- }
187
- // EXIT
188
106
  process.on("SIGINT", async () => {
189
- console.log(chalk_1.default.yellow("\nStopping watcher..."));
190
107
  await watcher.close();
191
108
  process.exit(0);
192
109
  });
@@ -41,7 +41,7 @@ class EventraEngine {
41
41
  this.graph.update(file, source);
42
42
  }
43
43
  }
44
- // SCAN FILE
44
+ // SCAN FILE (cached)
45
45
  scanFile(file, content, config) {
46
46
  file = this.normalize(file);
47
47
  const h = (0, hash_1.hash)(content);
@@ -55,7 +55,6 @@ class EventraEngine {
55
55
  if (!source || !checker) {
56
56
  return this.empty();
57
57
  }
58
- // update graph
59
58
  this.graph.update(file, source);
60
59
  const res = (0, scanner_1.scanSource)(source, checker, config, new Set(), undefined, this.wrapperCache);
61
60
  this.fileResults.set(file, res);
@@ -63,12 +62,19 @@ class EventraEngine {
63
62
  }
64
63
  // UPDATE FILE (incremental)
65
64
  updateFile(file, content, config) {
66
- this.wrapperCache.clear();
67
65
  file = this.normalize(file);
66
+ // EARLY EXIT
67
+ if (this.fileHash.get(file) === (0, hash_1.hash)(content)) {
68
+ return this.fileResults.get(file) ?? this.empty();
69
+ }
70
+ // invalidate wrapper cache
71
+ this.wrapperCache.clear();
68
72
  const res = this.scanFile(file, content, config);
69
73
  const dependents = this.graph.getAllDependentsDeep(file);
70
74
  for (const dep of dependents) {
71
75
  const abs = this.normalize(dep);
76
+ if (abs === file)
77
+ continue; // self-skip
72
78
  if ((0, boundary_1.isExternalFile)(abs))
73
79
  continue;
74
80
  let depContent = this.ts.getFileContent(abs);
@@ -106,15 +112,17 @@ class EventraEngine {
106
112
  }
107
113
  // REMOVE FILE
108
114
  removeFile(file, config) {
109
- this.wrapperCache.clear();
110
115
  file = this.normalize(file);
116
+ this.wrapperCache.clear();
111
117
  const dependents = this.graph.getAllDependentsDeep(file);
118
+ this.graph.remove(file);
112
119
  this.ts.removeFile(file);
113
120
  this.fileResults.delete(file);
114
121
  this.fileHash.delete(file);
115
- this.graph.remove(file);
116
122
  for (const dep of dependents) {
117
123
  const abs = this.normalize(dep);
124
+ if (abs === file)
125
+ continue;
118
126
  if ((0, boundary_1.isExternalFile)(abs))
119
127
  continue;
120
128
  let content = this.ts.getFileContent(abs);
@@ -126,10 +134,7 @@ class EventraEngine {
126
134
  continue;
127
135
  }
128
136
  }
129
- const h = (0, hash_1.hash)(content);
130
- if (this.fileHash.get(abs) === h)
131
- continue;
132
- this.fileHash.set(abs, h);
137
+ this.fileHash.delete(abs);
133
138
  this.ts.updateFile(abs, content);
134
139
  const source = this.ts.getSourceFile(abs);
135
140
  const checker = this.ts.getChecker();
@@ -21,7 +21,7 @@ class TSService {
21
21
  let compilerOptions = {
22
22
  allowJs: true,
23
23
  allowNonTsExtensions: true,
24
- jsx: typescript_1.default.JsxEmit.React,
24
+ jsx: typescript_1.default.JsxEmit.Preserve,
25
25
  jsxImportSource: "react",
26
26
  moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
27
27
  target: typescript_1.default.ScriptTarget.ESNext,
@@ -46,7 +46,15 @@ class TSService {
46
46
  this.compilerOptions = compilerOptions;
47
47
  this.baseUrl = baseUrl;
48
48
  this.paths = paths;
49
- const allFiles = typescript_1.default.sys.readDirectory(root, [".ts", ".tsx", ".js", ".jsx"], undefined, undefined);
49
+ const allFiles = typescript_1.default.sys.readDirectory(root, [
50
+ ".ts",
51
+ ".tsx",
52
+ ".js",
53
+ ".jsx",
54
+ ".vue",
55
+ ".svelte",
56
+ ".astro"
57
+ ], undefined, undefined);
50
58
  for (const f of allFiles) {
51
59
  this.fileNames.add(this.normalize(f));
52
60
  }
@@ -58,26 +66,31 @@ class TSService {
58
66
  file = this.normalize(file);
59
67
  return this.files.get(file)?.version.toString() ?? "0";
60
68
  },
69
+ getScriptKind: (fileName) => {
70
+ fileName = this.normalize(fileName);
71
+ if (fileName.endsWith(".tsx") ||
72
+ fileName.endsWith(".vue") ||
73
+ fileName.endsWith(".svelte") ||
74
+ fileName.endsWith(".astro")) {
75
+ return typescript_1.default.ScriptKind.TSX;
76
+ }
77
+ if (fileName.endsWith(".ts")) {
78
+ return typescript_1.default.ScriptKind.TS;
79
+ }
80
+ if (fileName.endsWith(".jsx")) {
81
+ return typescript_1.default.ScriptKind.JSX;
82
+ }
83
+ if (fileName.endsWith(".js")) {
84
+ return typescript_1.default.ScriptKind.JS;
85
+ }
86
+ return typescript_1.default.ScriptKind.Unknown;
87
+ },
61
88
  getScriptSnapshot: (file) => {
62
89
  file = this.normalize(file);
63
- // из памяти
64
90
  const f = this.files.get(file);
65
91
  if (f) {
66
92
  return typescript_1.default.ScriptSnapshot.fromString(f.content);
67
93
  }
68
- // virtual TSX (vue/svelte/astro)
69
- if (file.endsWith(".tsx")) {
70
- const original = file.replace(/\.tsx$/, "");
71
- if (fs_1.default.existsSync(original)) {
72
- const content = fs_1.default.readFileSync(original, "utf-8");
73
- this.files.set(file, {
74
- content,
75
- version: 1,
76
- });
77
- this.fileNames.add(file);
78
- return typescript_1.default.ScriptSnapshot.fromString(content);
79
- }
80
- }
81
94
  try {
82
95
  if (fs_1.default.existsSync(file)) {
83
96
  const content = fs_1.default.readFileSync(file, "utf-8");
@@ -110,12 +123,6 @@ class TSService {
110
123
  normalize(file) {
111
124
  return path_1.default.resolve(file).replace(/\\/g, "/");
112
125
  }
113
- ensureProgram() {
114
- try {
115
- this.service.getProgram();
116
- }
117
- catch { }
118
- }
119
126
  // PUBLIC API
120
127
  updateFile(file, content) {
121
128
  file = this.normalize(file);
@@ -125,13 +132,11 @@ class TSService {
125
132
  version: prev ? prev.version + 1 : 1,
126
133
  });
127
134
  this.fileNames.add(file);
128
- this.ensureProgram();
129
135
  }
130
136
  removeFile(file) {
131
137
  file = this.normalize(file);
132
138
  this.files.delete(file);
133
139
  this.fileNames.delete(file);
134
- this.ensureProgram();
135
140
  }
136
141
  getProgram() {
137
142
  return this.service.getProgram();