@eventra_dev/eventra-cli 0.1.24 → 0.1.25

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.
@@ -7,9 +7,11 @@ exports.check = check;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
9
  const promises_1 = __importDefault(require("fs/promises"));
10
+ const path_1 = __importDefault(require("path"));
10
11
  const config_1 = require("../utils/config");
11
12
  const engine_1 = require("../engine/engine");
12
13
  const processFile_1 = require("../utils/processFile");
14
+ const getVirtualFile_1 = require("../utils/getVirtualFile");
13
15
  async function check({ fix = false }) {
14
16
  const config = await (0, config_1.loadConfig)();
15
17
  if (!config) {
@@ -19,37 +21,59 @@ async function check({ fix = false }) {
19
21
  console.log(chalk_1.default.blue("Checking events..."));
20
22
  const files = await (0, fast_glob_1.default)(config.sync.include, {
21
23
  ignore: config.sync.exclude,
24
+ absolute: true,
22
25
  });
23
26
  const engine = new engine_1.EventraEngine(process.cwd());
27
+ const toScan = new Set();
28
+ const cache = new Map();
29
+ // COLLECT + CACHE
24
30
  for (const file of files) {
25
31
  try {
26
32
  const raw = await promises_1.default.readFile(file, "utf-8");
27
- const { content, virtualFile, deps } = (0, processFile_1.processFile)(file, raw);
28
- engine.scanFile(virtualFile, content, config);
29
- for (const dep of deps) {
30
- try {
31
- const depRaw = await promises_1.default.readFile(dep, "utf-8");
32
- const { content: depContent, virtualFile: depVirtual, } = (0, processFile_1.processFile)(dep, depRaw);
33
- engine.scanFile(depVirtual, depContent, config);
34
- }
35
- catch { }
33
+ if (raw.length > 2_000_000)
34
+ continue;
35
+ const parsed = (0, processFile_1.processFile)(file, raw);
36
+ cache.set(file, parsed);
37
+ toScan.add(file);
38
+ for (const dep of parsed.deps) {
39
+ toScan.add(path_1.default.resolve(dep));
36
40
  }
37
41
  }
38
- catch (err) {
39
- console.log(chalk_1.default.yellow(`Skipped: ${file}`));
42
+ catch { }
43
+ }
44
+ // PRELOAD
45
+ for (const file of toScan) {
46
+ 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);
55
+ }
56
+ catch { }
57
+ }
58
+ // SCAN
59
+ for (const file of toScan) {
60
+ try {
61
+ const parsed = cache.get(file);
62
+ if (!parsed)
63
+ continue;
64
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
65
+ engine.scanFile(virtual, parsed.content, config);
40
66
  }
67
+ catch { }
41
68
  }
69
+ // DIFF
42
70
  const found = new Set(engine.getAllEvents());
43
71
  const known = new Set(config.events ?? []);
44
72
  const newEvents = [...found].filter(e => !known.has(e));
45
73
  const removed = [...known].filter(e => !found.has(e));
46
74
  // FIX MODE
47
75
  if (fix) {
48
- const next = new Set();
49
- found.forEach(e => next.add(e));
50
- newEvents.forEach(e => console.log(chalk_1.default.green(`+ ${e}`)));
51
- removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
52
- config.events = [...next].sort();
76
+ config.events = [...found].sort();
53
77
  await (0, config_1.saveConfig)(config);
54
78
  console.log(chalk_1.default.green(`\nSynced (${config.events.length} events)`));
55
79
  return;
@@ -7,9 +7,11 @@ exports.sync = sync;
7
7
  const fast_glob_1 = __importDefault(require("fast-glob"));
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const chalk_1 = __importDefault(require("chalk"));
10
+ const path_1 = __importDefault(require("path"));
10
11
  const config_1 = require("../utils/config");
11
12
  const engine_1 = require("../engine/engine");
12
13
  const processFile_1 = require("../utils/processFile");
14
+ const getVirtualFile_1 = require("../utils/getVirtualFile");
13
15
  async function sync() {
14
16
  const config = await (0, config_1.loadConfig)();
15
17
  if (!config)
@@ -17,73 +19,73 @@ async function sync() {
17
19
  console.log(chalk_1.default.blue("Scanning..."));
18
20
  const files = await (0, fast_glob_1.default)(config.sync.include, {
19
21
  ignore: config.sync.exclude,
22
+ absolute: true,
20
23
  });
21
24
  if (!files.length) {
22
25
  console.log(chalk_1.default.yellow("No files found"));
23
26
  return;
24
27
  }
25
28
  const engine = new engine_1.EventraEngine(process.cwd());
26
- const detectedFn = new Set();
27
- const detectedCmp = new Map();
28
- const fileCache = [];
29
+ const toScan = new Set();
30
+ const cache = new Map();
31
+ // COLLECT + CACHE
29
32
  for (const file of files) {
30
33
  try {
31
34
  const raw = await promises_1.default.readFile(file, "utf-8");
32
- const { content, virtualFile, deps } = (0, processFile_1.processFile)(file, raw);
33
- engine["ts"].updateFile(virtualFile, content);
34
- fileCache.push({ file: virtualFile, content });
35
- // deps
36
- for (const dep of deps) {
37
- try {
38
- const depRaw = await promises_1.default.readFile(dep, "utf-8");
39
- const { content: depContent, virtualFile: depVirtual, } = (0, processFile_1.processFile)(dep, depRaw);
40
- engine["ts"].updateFile(depVirtual, depContent);
41
- fileCache.push({ file: depVirtual, content: depContent });
42
- }
43
- catch { }
35
+ if (raw.length > 2_000_000)
36
+ continue;
37
+ const parsed = (0, processFile_1.processFile)(file, raw);
38
+ cache.set(file, parsed);
39
+ toScan.add(file);
40
+ for (const dep of parsed.deps) {
41
+ toScan.add(path_1.default.resolve(dep));
44
42
  }
45
43
  }
46
44
  catch { }
47
45
  }
48
- for (const { file, content } of fileCache) {
46
+ // PRELOAD
47
+ for (const file of toScan) {
49
48
  try {
50
- const res = engine.scanFile(file, content, config);
51
- for (const w of res.detectedFunctionWrappers) {
52
- detectedFn.add(w);
49
+ let parsed = cache.get(file);
50
+ if (!parsed) {
51
+ const raw = await promises_1.default.readFile(file, "utf-8");
52
+ parsed = (0, processFile_1.processFile)(file, raw);
53
+ cache.set(file, parsed);
53
54
  }
55
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
56
+ engine.preloadFile(virtual, parsed.content);
57
+ }
58
+ catch { }
59
+ }
60
+ // SCAN
61
+ const detectedFn = new Set();
62
+ const detectedCmp = new Map();
63
+ for (const file of toScan) {
64
+ try {
65
+ const parsed = cache.get(file);
66
+ if (!parsed)
67
+ continue;
68
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
69
+ const res = engine.scanFile(virtual, parsed.content, config);
70
+ res.detectedFunctionWrappers.forEach(w => detectedFn.add(w));
54
71
  for (const [k, v] of res.detectedComponentWrappers) {
55
72
  detectedCmp.set(k, v);
56
73
  }
57
74
  }
58
- catch (err) {
59
- console.log(chalk_1.default.yellow(`Skipped: ${file}`));
60
- if (err instanceof Error) {
61
- console.log(chalk_1.default.gray(err.message));
62
- }
75
+ catch {
76
+ console.log(chalk_1.default.gray(`skip: ${file}`));
63
77
  }
64
78
  }
65
- // EVENTS
79
+ // RESULT
66
80
  const events = engine.getAllEvents().sort();
67
- const mergedEvents = new Set(config.events ?? []);
68
- events.forEach(e => mergedEvents.add(e));
69
- config.events = [...mergedEvents].sort();
70
- // FUNCTION WRAPPERS
81
+ config.events = events;
71
82
  config.functionWrappers = mergeUnique(config.functionWrappers ?? [], [...detectedFn].map(name => ({ name })), w => w.name);
72
- // COMPONENT WRAPPERS
73
83
  config.wrappers = mergeUnique(config.wrappers ?? [], [...detectedCmp.entries()].map(([name, prop]) => ({
74
84
  name,
75
85
  prop,
76
86
  })), w => `${w.name}:${w.prop}`);
77
87
  await (0, config_1.saveConfig)(config);
78
88
  console.log(chalk_1.default.green(`Found ${events.length} events`));
79
- if (detectedFn.size) {
80
- console.log(chalk_1.default.blue("\nDetected function wrappers:"));
81
- detectedFn.forEach(w => console.log(chalk_1.default.gray(`• ${w}`)));
82
- }
83
- if (detectedCmp.size) {
84
- console.log(chalk_1.default.blue("\nDetected component wrappers:"));
85
- detectedCmp.forEach((prop, name) => console.log(chalk_1.default.gray(`• ${name} (${prop})`)));
86
- }
87
89
  }
88
90
  // HELPERS
89
91
  function mergeUnique(a, b, key) {
@@ -12,12 +12,7 @@ const path_1 = __importDefault(require("path"));
12
12
  const config_1 = require("../utils/config");
13
13
  const engine_1 = require("../engine/engine");
14
14
  const processFile_1 = require("../utils/processFile");
15
- // stabile virtual mapping
16
- function getVirtualFile(file) {
17
- return /\.(vue|svelte|astro|html)$/i.test(file)
18
- ? file + ".tsx"
19
- : file;
20
- }
15
+ const getVirtualFile_1 = require("../utils/getVirtualFile");
21
16
  async function watch() {
22
17
  const config = await (0, config_1.loadConfig)();
23
18
  if (!config)
@@ -25,48 +20,82 @@ async function watch() {
25
20
  console.log(chalk_1.default.blue("Watching...\n"));
26
21
  const files = await (0, fast_glob_1.default)(config.sync.include, {
27
22
  ignore: config.sync.exclude,
23
+ absolute: true,
28
24
  });
29
25
  const engine = new engine_1.EventraEngine(process.cwd());
30
26
  const watchedDeps = new Set();
31
- // INITIAL LOAD
27
+ const cache = new Map();
28
+ const toScan = new Set();
29
+ // COLLECT + CACHE
32
30
  for (const file of files) {
33
31
  try {
34
- const abs = path_1.default.resolve(file);
35
- const raw = await promises_1.default.readFile(abs, "utf-8");
36
- const { content, deps } = (0, processFile_1.processFile)(abs, raw);
37
- const virtual = getVirtualFile(abs);
38
- engine.scanFile(virtual, content, config);
39
- // deps
40
- for (const dep of deps) {
41
- watchedDeps.add(path_1.default.resolve(dep));
32
+ const raw = await promises_1.default.readFile(file, "utf-8");
33
+ if (raw.length > 2_000_000)
34
+ continue; // safeguard
35
+ const parsed = (0, processFile_1.processFile)(file, raw);
36
+ cache.set(file, parsed);
37
+ toScan.add(file);
38
+ for (const dep of parsed.deps) {
39
+ const absDep = path_1.default.resolve(dep);
40
+ toScan.add(absDep);
42
41
  }
43
42
  }
44
43
  catch {
45
44
  console.log(chalk_1.default.gray(`skip: ${file}`));
46
45
  }
47
46
  }
47
+ // PRELOAD (TS PROGRAM)
48
+ for (const file of toScan) {
49
+ try {
50
+ let parsed = cache.get(file);
51
+ if (!parsed) {
52
+ const raw = await promises_1.default.readFile(file, "utf-8");
53
+ parsed = (0, processFile_1.processFile)(file, raw);
54
+ }
55
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
56
+ engine.preloadFile?.(virtual, parsed.content);
57
+ }
58
+ catch { }
59
+ }
60
+ // INITIAL SCAN
61
+ for (const file of toScan) {
62
+ try {
63
+ let parsed = cache.get(file);
64
+ if (!parsed) {
65
+ const raw = await promises_1.default.readFile(file, "utf-8");
66
+ parsed = (0, processFile_1.processFile)(file, raw);
67
+ }
68
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
69
+ engine.scanFile(virtual, parsed.content, config);
70
+ for (const dep of parsed.deps) {
71
+ watchedDeps.add(path_1.default.resolve(dep));
72
+ }
73
+ }
74
+ catch { }
75
+ }
48
76
  config.events = engine.getAllEvents().sort();
49
77
  await (0, config_1.saveConfig)(config);
50
78
  console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
51
79
  // QUEUE
52
80
  let locked = false;
53
- let queued = new Map();
81
+ const queued = new Map();
54
82
  let timer = null;
55
83
  const run = async () => {
56
- if (locked) {
57
- timer = setTimeout(run, 100);
84
+ if (locked)
58
85
  return;
59
- }
60
86
  locked = true;
61
87
  const batch = [...queued.values()];
62
88
  queued.clear();
63
89
  for (const item of batch) {
64
90
  try {
65
91
  const raw = await promises_1.default.readFile(item.real, "utf-8");
66
- const { content, deps } = (0, processFile_1.processFile)(item.real, raw);
67
- engine.updateFile(item.virtual, content, config);
92
+ if (raw.length > 2_000_000)
93
+ continue;
94
+ const parsed = (0, processFile_1.processFile)(item.real, raw);
95
+ cache.set(item.real, parsed);
96
+ engine.updateFile(item.virtual, parsed.content, config);
68
97
  // DEPS
69
- for (const dep of deps) {
98
+ for (const dep of parsed.deps) {
70
99
  const absDep = path_1.default.resolve(dep);
71
100
  if (!watchedDeps.has(absDep)) {
72
101
  watcher.add(absDep);
@@ -74,9 +103,10 @@ async function watch() {
74
103
  }
75
104
  try {
76
105
  const depRaw = await promises_1.default.readFile(absDep, "utf-8");
77
- const { content: depContent } = (0, processFile_1.processFile)(absDep, depRaw);
78
- const depVirtual = getVirtualFile(absDep);
79
- engine.updateFile(depVirtual, depContent, config);
106
+ const depParsed = (0, processFile_1.processFile)(absDep, depRaw);
107
+ cache.set(absDep, depParsed);
108
+ const depVirtual = (0, getVirtualFile_1.getVirtualFile)(absDep);
109
+ engine.updateFile(depVirtual, depParsed.content, config);
80
110
  }
81
111
  catch { }
82
112
  }
@@ -85,7 +115,7 @@ async function watch() {
85
115
  console.log(chalk_1.default.gray(`skip: ${item.real}`));
86
116
  }
87
117
  }
88
- // EVENTS DIFF
118
+ // DIFF EVENTS
89
119
  const next = engine.getAllEvents();
90
120
  const added = next.filter(e => !config.events.includes(e));
91
121
  const removed = config.events.filter(e => !next.includes(e));
@@ -101,16 +131,18 @@ async function watch() {
101
131
  // SCHEDULER
102
132
  const schedule = (file) => {
103
133
  const abs = path_1.default.resolve(file);
104
- const virtual = getVirtualFile(abs);
134
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
135
+ console.log(chalk_1.default.gray(`change: ${file}`));
105
136
  queued.set(abs, { real: abs, virtual });
106
137
  if (timer)
107
138
  clearTimeout(timer);
108
- timer = setTimeout(run, 300);
139
+ timer = setTimeout(run, 250);
109
140
  };
110
141
  // WATCHER
111
142
  const watcher = chokidar_1.default.watch(config.sync.include, {
112
143
  ignored: config.sync.exclude,
113
144
  ignoreInitial: true,
145
+ persistent: true,
114
146
  });
115
147
  watcher
116
148
  .on("add", schedule)
@@ -118,13 +150,17 @@ async function watch() {
118
150
  .on("unlink", async (file) => {
119
151
  const abs = path_1.default.resolve(file);
120
152
  console.log(chalk_1.default.gray(`remove: ${file}`));
121
- const virtual = getVirtualFile(abs);
153
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
122
154
  engine.removeFile(virtual, config);
155
+ watcher.unwatch(abs);
123
156
  watchedDeps.delete(abs);
124
157
  const next = engine.getAllEvents();
125
158
  config.events = next.sort();
126
159
  await (0, config_1.saveConfig)(config);
127
160
  console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
161
+ })
162
+ .on("error", (err) => {
163
+ console.error(chalk_1.default.red("Watcher error:"), err);
128
164
  });
129
165
  // WATCH DEPS
130
166
  for (const dep of watchedDeps) {
@@ -16,6 +16,9 @@ class EventraEngine {
16
16
  const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
17
17
  this.graph = new graph_1.DependencyGraph(baseUrl, paths);
18
18
  }
19
+ preloadFile(file, content) {
20
+ this.ts.updateFile(file, content);
21
+ }
19
22
  // SCAN FILE
20
23
  scanFile(file, content, config) {
21
24
  const h = (0, hash_1.hash)(content);
@@ -45,7 +48,6 @@ class EventraEngine {
45
48
  }
46
49
  // UPDATE FILE (incremental)
47
50
  updateFile(file, content, config) {
48
- // сначала обновляем текущий файл
49
51
  const res = this.scanFile(file, content, config);
50
52
  const dependents = this.graph.getAllDependentsDeep(file);
51
53
  for (const dep of dependents) {
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVirtualFile = getVirtualFile;
4
+ function getVirtualFile(file) {
5
+ return /\.(vue|svelte|astro|html)$/i.test(file)
6
+ ? file + ".tsx"
7
+ : file;
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,10 +1,12 @@
1
1
  import chalk from "chalk";
2
2
  import fg from "fast-glob";
3
3
  import fs from "fs/promises";
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 check({ fix = false }: { fix?: boolean }) {
10
12
  const config = await loadConfig();
@@ -18,36 +20,68 @@ export async function check({ fix = false }: { fix?: boolean }) {
18
20
 
19
21
  const files = await fg(config.sync.include, {
20
22
  ignore: config.sync.exclude,
23
+ absolute: true,
21
24
  });
22
25
 
23
26
  const engine = new EventraEngine(process.cwd());
24
27
 
28
+ const toScan = new Set<string>();
29
+
30
+ const cache = new Map<
31
+ string,
32
+ { content: string; deps: string[] }
33
+ >();
34
+
35
+ // COLLECT + CACHE
25
36
  for (const file of files) {
26
37
  try {
27
38
  const raw = await fs.readFile(file, "utf-8");
28
39
 
29
- const { content, virtualFile, deps } = processFile(file, raw);
40
+ if (raw.length > 2_000_000) continue;
41
+
42
+ const parsed = processFile(file, raw);
30
43
 
31
- engine.scanFile(virtualFile, content, config);
44
+ cache.set(file, parsed);
45
+ toScan.add(file);
32
46
 
33
- for (const dep of deps) {
34
- try {
35
- const depRaw = await fs.readFile(dep, "utf-8");
47
+ for (const dep of parsed.deps) {
48
+ toScan.add(path.resolve(dep));
49
+ }
50
+
51
+ } catch {}
52
+ }
36
53
 
37
- const {
38
- content: depContent,
39
- virtualFile: depVirtual,
40
- } = processFile(dep, depRaw);
54
+ // PRELOAD
55
+ for (const file of toScan) {
56
+ try {
57
+ let parsed = cache.get(file);
41
58
 
42
- engine.scanFile(depVirtual, depContent, config);
43
- } catch {}
59
+ if (!parsed) {
60
+ const raw = await fs.readFile(file, "utf-8");
61
+ parsed = processFile(file, raw);
62
+ cache.set(file, parsed);
44
63
  }
45
64
 
46
- } catch (err) {
47
- console.log(chalk.yellow(`Skipped: ${file}`));
48
- }
65
+ const virtual = getVirtualFile(file);
66
+ engine.preloadFile(virtual, parsed.content);
67
+
68
+ } catch {}
49
69
  }
50
70
 
71
+ // SCAN
72
+ for (const file of toScan) {
73
+ try {
74
+ const parsed = cache.get(file);
75
+ if (!parsed) continue;
76
+
77
+ const virtual = getVirtualFile(file);
78
+
79
+ engine.scanFile(virtual, parsed.content, config);
80
+
81
+ } catch {}
82
+ }
83
+
84
+ // DIFF
51
85
  const found = new Set(engine.getAllEvents());
52
86
  const known = new Set(config.events ?? []);
53
87
 
@@ -56,26 +90,12 @@ export async function check({ fix = false }: { fix?: boolean }) {
56
90
 
57
91
  // FIX MODE
58
92
  if (fix) {
59
- const next = new Set<string>();
60
-
61
- found.forEach(e => next.add(e));
62
-
63
- newEvents.forEach(e =>
64
- console.log(chalk.green(`+ ${e}`))
65
- );
66
-
67
- removed.forEach(e =>
68
- console.log(chalk.red(`- ${e}`))
69
- );
70
-
71
- config.events = [...next].sort();
72
-
93
+ config.events = [...found].sort();
73
94
  await saveConfig(config);
74
95
 
75
96
  console.log(
76
97
  chalk.green(`\nSynced (${config.events.length} events)`)
77
98
  );
78
-
79
99
  return;
80
100
  }
81
101
 
@@ -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,19 +7,13 @@ 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;
21
- }
22
-
23
17
  export async function watch() {
24
18
  const config = await loadConfig();
25
19
  if (!config) return;
@@ -28,26 +22,33 @@ export async function watch() {
28
22
 
29
23
  const files = await fg(config.sync.include, {
30
24
  ignore: config.sync.exclude,
25
+ absolute: true,
31
26
  });
32
27
 
33
28
  const engine = new EventraEngine(process.cwd());
34
-
35
29
  const watchedDeps = new Set<string>();
30
+ const cache = new Map<
31
+ string,
32
+ { content: string; deps: string[] }
33
+ >();
36
34
 
37
- // INITIAL LOAD
35
+ const toScan = new Set<string>();
36
+
37
+ // COLLECT + CACHE
38
38
  for (const file of files) {
39
39
  try {
40
- const abs = path.resolve(file);
41
- const raw = await fs.readFile(abs, "utf-8");
40
+ const raw = await fs.readFile(file, "utf-8");
42
41
 
43
- const { content, deps } = processFile(abs, raw);
44
- const virtual = getVirtualFile(abs);
42
+ if (raw.length > 2_000_000) continue; // safeguard
45
43
 
46
- engine.scanFile(virtual, content, config);
44
+ const parsed = processFile(file, raw);
47
45
 
48
- // deps
49
- for (const dep of deps) {
50
- watchedDeps.add(path.resolve(dep));
46
+ cache.set(file, parsed);
47
+ toScan.add(file);
48
+
49
+ for (const dep of parsed.deps) {
50
+ const absDep = path.resolve(dep);
51
+ toScan.add(absDep);
51
52
  }
52
53
 
53
54
  } catch {
@@ -55,6 +56,44 @@ export async function watch() {
55
56
  }
56
57
  }
57
58
 
59
+ // PRELOAD (TS PROGRAM)
60
+ for (const file of toScan) {
61
+ try {
62
+ let parsed = cache.get(file);
63
+
64
+ if (!parsed) {
65
+ const raw = await fs.readFile(file, "utf-8");
66
+ parsed = processFile(file, raw);
67
+ }
68
+
69
+ const virtual = getVirtualFile(file);
70
+
71
+ engine.preloadFile?.(virtual, parsed.content);
72
+
73
+ } catch {}
74
+ }
75
+
76
+ // INITIAL SCAN
77
+ for (const file of toScan) {
78
+ try {
79
+ let parsed = cache.get(file);
80
+
81
+ if (!parsed) {
82
+ const raw = await fs.readFile(file, "utf-8");
83
+ parsed = processFile(file, raw);
84
+ }
85
+
86
+ const virtual = getVirtualFile(file);
87
+
88
+ engine.scanFile(virtual, parsed.content, config);
89
+
90
+ for (const dep of parsed.deps) {
91
+ watchedDeps.add(path.resolve(dep));
92
+ }
93
+
94
+ } catch {}
95
+ }
96
+
58
97
  config.events = engine.getAllEvents().sort();
59
98
  await saveConfig(config);
60
99
 
@@ -64,15 +103,11 @@ export async function watch() {
64
103
 
65
104
  // QUEUE
66
105
  let locked = false;
67
- let queued = new Map<string, QueueItem>();
106
+ const queued = new Map<string, QueueItem>();
68
107
  let timer: NodeJS.Timeout | null = null;
69
108
 
70
109
  const run = async () => {
71
- if (locked) {
72
- timer = setTimeout(run, 100);
73
- return;
74
- }
75
-
110
+ if (locked) return;
76
111
  locked = true;
77
112
 
78
113
  const batch = [...queued.values()];
@@ -82,12 +117,16 @@ export async function watch() {
82
117
  try {
83
118
  const raw = await fs.readFile(item.real, "utf-8");
84
119
 
85
- const { content, deps } = processFile(item.real, raw);
120
+ if (raw.length > 2_000_000) continue;
86
121
 
87
- engine.updateFile(item.virtual, content, config);
122
+ const parsed = processFile(item.real, raw);
123
+
124
+ cache.set(item.real, parsed);
125
+
126
+ engine.updateFile(item.virtual, parsed.content, config);
88
127
 
89
128
  // DEPS
90
- for (const dep of deps) {
129
+ for (const dep of parsed.deps) {
91
130
  const absDep = path.resolve(dep);
92
131
 
93
132
  if (!watchedDeps.has(absDep)) {
@@ -97,11 +136,13 @@ export async function watch() {
97
136
 
98
137
  try {
99
138
  const depRaw = await fs.readFile(absDep, "utf-8");
100
- const { content: depContent } = processFile(absDep, depRaw);
139
+ const depParsed = processFile(absDep, depRaw);
140
+
141
+ cache.set(absDep, depParsed);
101
142
 
102
143
  const depVirtual = getVirtualFile(absDep);
103
144
 
104
- engine.updateFile(depVirtual, depContent, config);
145
+ engine.updateFile(depVirtual, depParsed.content, config);
105
146
  } catch {}
106
147
  }
107
148
 
@@ -110,7 +151,7 @@ export async function watch() {
110
151
  }
111
152
  }
112
153
 
113
- // EVENTS DIFF
154
+ // DIFF EVENTS
114
155
  const next = engine.getAllEvents();
115
156
 
116
157
  const added = next.filter(e => !config.events.includes(e));
@@ -136,16 +177,19 @@ export async function watch() {
136
177
  const abs = path.resolve(file);
137
178
  const virtual = getVirtualFile(abs);
138
179
 
180
+ console.log(chalk.gray(`change: ${file}`));
181
+
139
182
  queued.set(abs, { real: abs, virtual });
140
183
 
141
184
  if (timer) clearTimeout(timer);
142
- timer = setTimeout(run, 300);
185
+ timer = setTimeout(run, 250);
143
186
  };
144
187
 
145
188
  // WATCHER
146
189
  const watcher = chokidar.watch(config.sync.include, {
147
190
  ignored: config.sync.exclude,
148
191
  ignoreInitial: true,
192
+ persistent: true,
149
193
  });
150
194
 
151
195
  watcher
@@ -160,6 +204,7 @@ export async function watch() {
160
204
 
161
205
  engine.removeFile(virtual, config);
162
206
 
207
+ watcher.unwatch(abs);
163
208
  watchedDeps.delete(abs);
164
209
 
165
210
  const next = engine.getAllEvents();
@@ -170,6 +215,9 @@ export async function watch() {
170
215
  console.log(
171
216
  chalk.gray(`Total: ${config.events.length} events\n`)
172
217
  );
218
+ })
219
+ .on("error", (err) => {
220
+ console.error(chalk.red("Watcher error:"), err);
173
221
  });
174
222
 
175
223
  // WATCH DEPS
@@ -18,6 +18,9 @@ export class EventraEngine {
18
18
  const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
19
19
  this.graph = new DependencyGraph(baseUrl, paths);
20
20
  }
21
+ preloadFile(file: string, content: string) {
22
+ this.ts.updateFile(file, content);
23
+ }
21
24
 
22
25
  // SCAN FILE
23
26
  scanFile(
@@ -66,7 +69,6 @@ export class EventraEngine {
66
69
  content: string,
67
70
  config: EventraConfig
68
71
  ) {
69
- // сначала обновляем текущий файл
70
72
  const res = this.scanFile(file, content, config);
71
73
 
72
74
  const dependents = this.graph.getAllDependentsDeep(file);
@@ -0,0 +1,5 @@
1
+ export function getVirtualFile(file: string) {
2
+ return /\.(vue|svelte|astro|html)$/i.test(file)
3
+ ? file + ".tsx"
4
+ : file;
5
+ }