@eventra_dev/eventra-cli 0.1.23 → 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,6 +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
+ const getVirtualFile_1 = require("../utils/getVirtualFile");
15
16
  async function watch() {
16
17
  const config = await (0, config_1.loadConfig)();
17
18
  if (!config)
@@ -19,56 +20,102 @@ async function watch() {
19
20
  console.log(chalk_1.default.blue("Watching...\n"));
20
21
  const files = await (0, fast_glob_1.default)(config.sync.include, {
21
22
  ignore: config.sync.exclude,
23
+ absolute: true,
22
24
  });
23
25
  const engine = new engine_1.EventraEngine(process.cwd());
24
- const fileCache = [];
25
- // PRELOAD TS
26
+ const watchedDeps = new Set();
27
+ const cache = new Map();
28
+ const toScan = new Set();
29
+ // COLLECT + CACHE
26
30
  for (const file of files) {
27
31
  try {
28
- const abs = path_1.default.resolve(file);
29
- const raw = await promises_1.default.readFile(abs, "utf-8");
30
- const { content, virtualFile } = (0, processFile_1.processFile)(abs, raw);
31
- fileCache.push({ file: virtualFile, content });
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);
41
+ }
32
42
  }
33
43
  catch {
34
44
  console.log(chalk_1.default.gray(`skip: ${file}`));
35
45
  }
36
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
+ }
37
60
  // INITIAL SCAN
38
- for (const { file, content } of fileCache) {
39
- engine.scanFile(file, content, config);
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 { }
40
75
  }
41
76
  config.events = engine.getAllEvents().sort();
42
77
  await (0, config_1.saveConfig)(config);
43
78
  console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
79
+ // QUEUE
44
80
  let locked = false;
45
- let queued = new Set();
81
+ const queued = new Map();
46
82
  let timer = null;
47
83
  const run = async () => {
48
84
  if (locked)
49
85
  return;
50
86
  locked = true;
51
- const batch = [...queued];
87
+ const batch = [...queued.values()];
52
88
  queued.clear();
53
- for (const file of batch) {
89
+ for (const item of batch) {
54
90
  try {
55
- const raw = await promises_1.default.readFile(file, "utf-8");
56
- const { content, virtualFile, deps } = (0, processFile_1.processFile)(file, raw);
57
- engine.updateFile(virtualFile, content, config);
58
- // deps
59
- for (const dep of deps) {
91
+ const raw = await promises_1.default.readFile(item.real, "utf-8");
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);
97
+ // DEPS
98
+ for (const dep of parsed.deps) {
99
+ const absDep = path_1.default.resolve(dep);
100
+ if (!watchedDeps.has(absDep)) {
101
+ watcher.add(absDep);
102
+ watchedDeps.add(absDep);
103
+ }
60
104
  try {
61
- const depRaw = await promises_1.default.readFile(dep, "utf-8");
62
- const { content: depContent, virtualFile: depVirtual, } = (0, processFile_1.processFile)(dep, depRaw);
63
- engine.updateFile(depVirtual, depContent, config);
105
+ const depRaw = await promises_1.default.readFile(absDep, "utf-8");
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);
64
110
  }
65
111
  catch { }
66
112
  }
67
113
  }
68
114
  catch {
69
- console.log(chalk_1.default.gray(`skip: ${file}`));
115
+ console.log(chalk_1.default.gray(`skip: ${item.real}`));
70
116
  }
71
117
  }
118
+ // DIFF EVENTS
72
119
  const next = engine.getAllEvents();
73
120
  const added = next.filter(e => !config.events.includes(e));
74
121
  const removed = config.events.filter(e => !next.includes(e));
@@ -81,15 +128,21 @@ async function watch() {
81
128
  }
82
129
  locked = false;
83
130
  };
131
+ // SCHEDULER
84
132
  const schedule = (file) => {
85
- queued.add(path_1.default.resolve(file));
133
+ const abs = path_1.default.resolve(file);
134
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
135
+ console.log(chalk_1.default.gray(`change: ${file}`));
136
+ queued.set(abs, { real: abs, virtual });
86
137
  if (timer)
87
138
  clearTimeout(timer);
88
- timer = setTimeout(run, 200);
139
+ timer = setTimeout(run, 250);
89
140
  };
141
+ // WATCHER
90
142
  const watcher = chokidar_1.default.watch(config.sync.include, {
91
143
  ignored: config.sync.exclude,
92
144
  ignoreInitial: true,
145
+ persistent: true,
93
146
  });
94
147
  watcher
95
148
  .on("add", schedule)
@@ -97,13 +150,23 @@ async function watch() {
97
150
  .on("unlink", async (file) => {
98
151
  const abs = path_1.default.resolve(file);
99
152
  console.log(chalk_1.default.gray(`remove: ${file}`));
100
- const { virtualFile } = (0, processFile_1.processFile)(abs, "");
101
- engine.removeFile(virtualFile, config);
153
+ const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
154
+ engine.removeFile(virtual, config);
155
+ watcher.unwatch(abs);
156
+ watchedDeps.delete(abs);
102
157
  const next = engine.getAllEvents();
103
158
  config.events = next.sort();
104
159
  await (0, config_1.saveConfig)(config);
105
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);
106
164
  });
165
+ // WATCH DEPS
166
+ for (const dep of watchedDeps) {
167
+ watcher.add(dep);
168
+ }
169
+ // EXIT
107
170
  process.on("SIGINT", async () => {
108
171
  console.log(chalk_1.default.yellow("\nStopping watcher..."));
109
172
  await watcher.close();
@@ -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.23",
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,6 +7,12 @@ 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";
11
+
12
+ type QueueItem = {
13
+ real: string;
14
+ virtual: string;
15
+ };
10
16
 
11
17
  export async function watch() {
12
18
  const config = await loadConfig();
@@ -16,27 +22,76 @@ export async function watch() {
16
22
 
17
23
  const files = await fg(config.sync.include, {
18
24
  ignore: config.sync.exclude,
25
+ absolute: true,
19
26
  });
20
27
 
21
28
  const engine = new EventraEngine(process.cwd());
29
+ const watchedDeps = new Set<string>();
30
+ const cache = new Map<
31
+ string,
32
+ { content: string; deps: string[] }
33
+ >();
22
34
 
23
- const fileCache: { file: string; content: string }[] = [];
35
+ const toScan = new Set<string>();
24
36
 
25
- // PRELOAD TS
37
+ // COLLECT + CACHE
26
38
  for (const file of files) {
27
39
  try {
28
- const abs = path.resolve(file);
29
- const raw = await fs.readFile(abs, "utf-8");
30
- const { content, virtualFile } = processFile(abs, raw);
31
- fileCache.push({ file: virtualFile, content });
40
+ const raw = await fs.readFile(file, "utf-8");
41
+
42
+ if (raw.length > 2_000_000) continue; // safeguard
43
+
44
+ const parsed = processFile(file, raw);
45
+
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);
52
+ }
53
+
32
54
  } catch {
33
55
  console.log(chalk.gray(`skip: ${file}`));
34
56
  }
35
57
  }
36
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
+
37
76
  // INITIAL SCAN
38
- for (const { file, content } of fileCache) {
39
- engine.scanFile(file, content, config);
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 {}
40
95
  }
41
96
 
42
97
  config.events = engine.getAllEvents().sort();
@@ -46,45 +101,57 @@ export async function watch() {
46
101
  chalk.gray(`Initial: ${config.events.length} events\n`)
47
102
  );
48
103
 
104
+ // QUEUE
49
105
  let locked = false;
50
- let queued = new Set<string>();
106
+ const queued = new Map<string, QueueItem>();
51
107
  let timer: NodeJS.Timeout | null = null;
52
108
 
53
109
  const run = async () => {
54
110
  if (locked) return;
55
111
  locked = true;
56
112
 
57
- const batch = [...queued];
113
+ const batch = [...queued.values()];
58
114
  queued.clear();
59
115
 
60
- for (const file of batch) {
116
+ for (const item of batch) {
61
117
  try {
62
- const raw = await fs.readFile(file, "utf-8");
118
+ const raw = await fs.readFile(item.real, "utf-8");
63
119
 
64
- const { content, virtualFile, deps } = processFile(file, raw);
120
+ if (raw.length > 2_000_000) continue;
65
121
 
66
- engine.updateFile(virtualFile, 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);
127
+
128
+ // DEPS
129
+ for (const dep of parsed.deps) {
130
+ const absDep = path.resolve(dep);
131
+
132
+ if (!watchedDeps.has(absDep)) {
133
+ watcher.add(absDep);
134
+ watchedDeps.add(absDep);
135
+ }
67
136
 
68
- // deps
69
- for (const dep of deps) {
70
137
  try {
71
- const depRaw = await fs.readFile(dep, "utf-8");
138
+ const depRaw = await fs.readFile(absDep, "utf-8");
139
+ const depParsed = processFile(absDep, depRaw);
72
140
 
73
- const {
74
- content: depContent,
75
- virtualFile: depVirtual,
76
- } = processFile(dep, depRaw);
141
+ cache.set(absDep, depParsed);
77
142
 
78
- engine.updateFile(depVirtual, depContent, config);
143
+ const depVirtual = getVirtualFile(absDep);
79
144
 
145
+ engine.updateFile(depVirtual, depParsed.content, config);
80
146
  } catch {}
81
147
  }
82
148
 
83
149
  } catch {
84
- console.log(chalk.gray(`skip: ${file}`));
150
+ console.log(chalk.gray(`skip: ${item.real}`));
85
151
  }
86
152
  }
87
153
 
154
+ // DIFF EVENTS
88
155
  const next = engine.getAllEvents();
89
156
 
90
157
  const added = next.filter(e => !config.events.includes(e));
@@ -105,16 +172,24 @@ export async function watch() {
105
172
  locked = false;
106
173
  };
107
174
 
175
+ // SCHEDULER
108
176
  const schedule = (file: string) => {
109
- queued.add(path.resolve(file));
177
+ const abs = path.resolve(file);
178
+ const virtual = getVirtualFile(abs);
179
+
180
+ console.log(chalk.gray(`change: ${file}`));
181
+
182
+ queued.set(abs, { real: abs, virtual });
110
183
 
111
184
  if (timer) clearTimeout(timer);
112
- timer = setTimeout(run, 200);
185
+ timer = setTimeout(run, 250);
113
186
  };
114
187
 
188
+ // WATCHER
115
189
  const watcher = chokidar.watch(config.sync.include, {
116
190
  ignored: config.sync.exclude,
117
191
  ignoreInitial: true,
192
+ persistent: true,
118
193
  });
119
194
 
120
195
  watcher
@@ -125,9 +200,12 @@ export async function watch() {
125
200
 
126
201
  console.log(chalk.gray(`remove: ${file}`));
127
202
 
128
- const { virtualFile } = processFile(abs, "");
203
+ const virtual = getVirtualFile(abs);
204
+
205
+ engine.removeFile(virtual, config);
129
206
 
130
- engine.removeFile(virtualFile, config);
207
+ watcher.unwatch(abs);
208
+ watchedDeps.delete(abs);
131
209
 
132
210
  const next = engine.getAllEvents();
133
211
 
@@ -137,8 +215,17 @@ export async function watch() {
137
215
  console.log(
138
216
  chalk.gray(`Total: ${config.events.length} events\n`)
139
217
  );
218
+ })
219
+ .on("error", (err) => {
220
+ console.error(chalk.red("Watcher error:"), err);
140
221
  });
141
222
 
223
+ // WATCH DEPS
224
+ for (const dep of watchedDeps) {
225
+ watcher.add(dep);
226
+ }
227
+
228
+ // EXIT
142
229
  process.on("SIGINT", async () => {
143
230
  console.log(chalk.yellow("\nStopping watcher..."));
144
231
  await watcher.close();
@@ -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
+ }