@eventra_dev/eventra-cli 0.0.19 → 0.1.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.
Files changed (41) hide show
  1. package/dist/commands/check.js +17 -117
  2. package/dist/commands/sync.js +20 -115
  3. package/dist/commands/watch.js +107 -69
  4. package/dist/engine/callResolver.js +34 -0
  5. package/dist/engine/engine.js +47 -0
  6. package/dist/engine/graph.js +25 -0
  7. package/dist/engine/hash.js +10 -0
  8. package/dist/engine/languageService.js +67 -0
  9. package/dist/engine/resolver.js +69 -0
  10. package/dist/engine/scanner.js +50 -0
  11. package/dist/utils/parsers/vue.js +16 -12
  12. package/dist/utils/processFile.js +20 -0
  13. package/package.json +1 -1
  14. package/src/commands/check.ts +19 -151
  15. package/src/commands/sync.ts +23 -194
  16. package/src/commands/watch.ts +119 -66
  17. package/src/engine/callResolver.ts +43 -0
  18. package/src/engine/engine.ts +60 -0
  19. package/src/engine/graph.ts +23 -0
  20. package/src/engine/hash.ts +5 -0
  21. package/src/engine/languageService.ts +73 -0
  22. package/src/engine/resolver.ts +86 -0
  23. package/src/engine/scanner.ts +61 -0
  24. package/src/types.ts +5 -6
  25. package/src/utils/parsers/vue.ts +19 -20
  26. package/src/utils/processFile.ts +18 -0
  27. package/tests/fixtures/backend/express/app.ts +167 -12
  28. package/tests/fixtures/wrappers/component/test.tsx +1 -1
  29. package/tests/run.ts +4 -17
  30. package/dist/utils/detect-aliases.js +0 -39
  31. package/dist/utils/detect-wrappers.js +0 -84
  32. package/dist/utils/extract.js +0 -56
  33. package/dist/utils/scanners/component-wrappers.js +0 -73
  34. package/dist/utils/scanners/function-wrappers.js +0 -83
  35. package/dist/utils/scanners/track.js +0 -64
  36. package/src/utils/detect-aliases.ts +0 -46
  37. package/src/utils/detect-wrappers.ts +0 -119
  38. package/src/utils/extract.ts +0 -72
  39. package/src/utils/scanners/component-wrappers.ts +0 -109
  40. package/src/utils/scanners/function-wrappers.ts +0 -137
  41. package/src/utils/scanners/track.ts +0 -99
@@ -6,17 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.check = check;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
- const ts_morph_1 = require("ts-morph");
10
9
  const promises_1 = __importDefault(require("fs/promises"));
11
- const inquirer_1 = __importDefault(require("inquirer"));
12
10
  const config_1 = require("../utils/config");
13
- const router_1 = require("../utils/parsers/router");
14
- const vue_1 = require("../utils/parsers/vue");
15
- const svelte_1 = require("../utils/parsers/svelte");
16
- const astro_1 = require("../utils/parsers/astro");
17
- const track_1 = require("../utils/scanners/track");
18
- const function_wrappers_1 = require("../utils/scanners/function-wrappers");
19
- const component_wrappers_1 = require("../utils/scanners/component-wrappers");
11
+ const engine_1 = require("../engine/engine");
12
+ const processFile_1 = require("../utils/processFile");
20
13
  async function check({ fix = false }) {
21
14
  const config = await (0, config_1.loadConfig)();
22
15
  if (!config) {
@@ -24,129 +17,36 @@ async function check({ fix = false }) {
24
17
  process.exit(1);
25
18
  }
26
19
  console.log(chalk_1.default.blue("Checking events..."));
27
- const project = new ts_morph_1.Project();
28
- const knownEvents = new Set(config.events ?? []);
29
- const aliases = { ...(config.aliases ?? {}) };
30
- const foundEvents = new Map();
31
- const newEvents = new Map();
32
- const unresolvedDynamic = new Map();
33
20
  const files = await (0, fast_glob_1.default)(config.sync.include, {
34
- ignore: config.sync.exclude
21
+ ignore: config.sync.exclude,
35
22
  });
23
+ const engine = new engine_1.EventraEngine(process.cwd());
36
24
  for (const file of files) {
37
25
  try {
38
- const stat = await promises_1.default.stat(file);
39
- if (stat.size > 2 * 1024 * 1024) {
40
- continue;
41
- }
42
- const parser = (0, router_1.detectParser)(file);
43
- let content = await promises_1.default.readFile(file, "utf-8");
44
- if (parser === "vue")
45
- content = (0, vue_1.parseVue)(content);
46
- if (parser === "svelte")
47
- content = (0, svelte_1.parseSvelte)(content);
48
- if (parser === "astro")
49
- content = (0, astro_1.parseAstro)(content);
50
- const source = project.createSourceFile(file, content, {
51
- overwrite: true
52
- });
53
- const found = [
54
- ...(0, track_1.scanTrack)(source, config, aliases),
55
- ...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? [], aliases),
56
- ...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [], aliases)
57
- ];
58
- for (const event of found) {
59
- const { value, dynamic } = event;
60
- if (!value)
61
- continue;
62
- // skip
63
- if (aliases[value] === "__skip__")
64
- continue;
65
- // alias resolve
66
- if (value in aliases) {
67
- foundEvents.set(aliases[value], {
68
- ...event,
69
- value: aliases[value]
70
- });
71
- continue;
72
- }
73
- if (dynamic) {
74
- unresolvedDynamic.set(value, event);
75
- continue;
76
- }
77
- foundEvents.set(value, event);
78
- }
79
- project.removeSourceFile(source);
26
+ const raw = await promises_1.default.readFile(file, "utf-8");
27
+ const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
28
+ engine.scanFile(virtualFile, content);
80
29
  }
81
30
  catch {
82
31
  console.log(chalk_1.default.gray(`Skipped: ${file}`));
83
32
  }
84
33
  }
85
- // find new
86
- for (const [key, event] of foundEvents) {
87
- if (!knownEvents.has(key)) {
88
- newEvents.set(key, event);
89
- }
90
- }
91
- // FIX MODE
34
+ const found = new Set(engine.getAllEvents());
35
+ const known = new Set(config.events ?? []);
36
+ const newEvents = [...found].filter(e => !known.has(e));
92
37
  if (fix) {
93
- console.log(chalk_1.default.yellow("\nFix mode\n"));
94
- // add new events
95
- for (const [name] of newEvents) {
96
- config.events.push(name);
97
- console.log(chalk_1.default.green(`+ ${name}`));
98
- }
99
- // resolve dynamic
100
- for (const [value] of unresolvedDynamic) {
101
- const { action } = await inquirer_1.default.prompt([
102
- {
103
- type: "list",
104
- name: "action",
105
- message: `Dynamic: ${value}`,
106
- choices: [
107
- { name: "Alias", value: "alias" },
108
- { name: "Skip", value: "skip" }
109
- ]
110
- }
111
- ]);
112
- if (action === "skip") {
113
- aliases[value] = "__skip__";
114
- continue;
115
- }
116
- const { name } = await inquirer_1.default.prompt([
117
- {
118
- type: "input",
119
- name: "name",
120
- message: "Event name:"
121
- }
122
- ]);
123
- const normalized = name.trim().toLowerCase();
124
- if (!normalized)
125
- continue;
126
- aliases[value] = normalized;
127
- config.events.push(normalized);
128
- console.log(chalk_1.default.green(`${value} → ${normalized}`));
129
- }
130
- config.aliases = Object.fromEntries(Object.entries(aliases).sort());
38
+ newEvents.forEach(e => {
39
+ config.events.push(e);
40
+ console.log(chalk_1.default.green(`+ ${e}`));
41
+ });
131
42
  config.events = [...new Set(config.events)].sort();
132
43
  await (0, config_1.saveConfig)(config);
133
- console.log(chalk_1.default.green("\n✔ Fixed"));
44
+ console.log(chalk_1.default.green("\nFixed"));
134
45
  return;
135
46
  }
136
- // NORMAL MODE
137
- let hasError = false;
138
- if (unresolvedDynamic.size) {
139
- hasError = true;
140
- console.log(chalk_1.default.red("\nDynamic events:"));
141
- unresolvedDynamic.forEach((e) => console.log(chalk_1.default.red(`- ${e.value} (${e.file}:${e.line})`)));
142
- }
143
- if (newEvents.size) {
144
- hasError = true;
47
+ if (newEvents.length) {
145
48
  console.log(chalk_1.default.red("\nNew events:"));
146
- newEvents.forEach((e) => console.log(chalk_1.default.red(`+ ${e.value} (${e.file}:${e.line})`)));
147
- }
148
- if (hasError) {
149
- console.log(chalk_1.default.red("\nEvent check failed"));
49
+ newEvents.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
150
50
  process.exit(1);
151
51
  }
152
52
  console.log(chalk_1.default.green("\nAll good"));
@@ -4,133 +4,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.sync = sync;
7
- const chalk_1 = __importDefault(require("chalk"));
8
7
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
- const ts_morph_1 = require("ts-morph");
10
8
  const promises_1 = __importDefault(require("fs/promises"));
11
- const inquirer_1 = __importDefault(require("inquirer"));
9
+ const chalk_1 = __importDefault(require("chalk"));
12
10
  const config_1 = require("../utils/config");
13
- const detect_wrappers_1 = require("../utils/detect-wrappers");
14
- const detect_aliases_1 = require("../utils/detect-aliases");
15
- const router_1 = require("../utils/parsers/router");
16
- const vue_1 = require("../utils/parsers/vue");
17
- const svelte_1 = require("../utils/parsers/svelte");
18
- const astro_1 = require("../utils/parsers/astro");
19
- const track_1 = require("../utils/scanners/track");
20
- const function_wrappers_1 = require("../utils/scanners/function-wrappers");
21
- const component_wrappers_1 = require("../utils/scanners/component-wrappers");
11
+ const engine_1 = require("../engine/engine");
12
+ const processFile_1 = require("../utils/processFile");
22
13
  async function sync() {
23
14
  const config = await (0, config_1.loadConfig)();
24
- if (!config) {
25
- console.log(chalk_1.default.red("Run 'eventra init'"));
15
+ if (!config)
26
16
  return;
27
- }
28
- console.log(chalk_1.default.blue("Scanning project..."));
29
- const project = new ts_morph_1.Project();
30
- const events = new Set();
31
- const aliases = config.aliases ?? {};
32
- const asked = new Set();
17
+ console.log(chalk_1.default.blue("Scanning..."));
33
18
  const files = await (0, fast_glob_1.default)(config.sync.include, {
34
- ignore: config.sync.exclude
19
+ ignore: config.sync.exclude,
35
20
  });
21
+ if (!files.length) {
22
+ console.log(chalk_1.default.yellow("No files found"));
23
+ return;
24
+ }
25
+ const engine = new engine_1.EventraEngine(process.cwd());
36
26
  for (const file of files) {
37
27
  try {
38
- const parser = (0, router_1.detectParser)(file);
39
- let content = await promises_1.default.readFile(file, "utf-8");
40
- if (parser === "vue")
41
- content = (0, vue_1.parseVue)(content);
42
- if (parser === "svelte")
43
- content = (0, svelte_1.parseSvelte)(content);
44
- if (parser === "astro")
45
- content = (0, astro_1.parseAstro)(content);
46
- const virtualFile = parser === "ts" ? file : file + ".tsx";
47
- const source = project.createSourceFile(virtualFile, content, { overwrite: true });
48
- // AUTO ALIASES
49
- const detectedAliases = (0, detect_aliases_1.detectAliases)(source);
50
- for (const key in detectedAliases) {
51
- if (!(key in aliases)) {
52
- aliases[key] = detectedAliases[key];
53
- console.log(chalk_1.default.green(`Detected alias: ${key} → ${detectedAliases[key]}`));
54
- }
55
- }
56
- // AUTO WRAPPERS
57
- const detected = (0, detect_wrappers_1.detectWrappers)(source);
58
- // function wrappers
59
- for (const fn of detected.functions) {
60
- if (!config.functionWrappers?.some((w) => w.name === fn.name)) {
61
- config.functionWrappers.push(fn);
62
- console.log(chalk_1.default.green(`Detected function wrapper: ${fn.name}`));
63
- }
64
- }
65
- // component wrappers
66
- for (const comp of detected.components) {
67
- if (!config.wrappers?.some((w) => w.name === comp.name)) {
68
- config.wrappers.push(comp);
69
- console.log(chalk_1.default.green(`Detected component wrapper: ${comp.name} (prop: ${comp.prop})`));
70
- }
71
- }
72
- // SCAN EVENTS
73
- const found = [
74
- ...(0, track_1.scanTrack)(source, config, aliases),
75
- ...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? [], aliases),
76
- ...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [], aliases)
77
- ];
78
- for (const event of found) {
79
- const { value, dynamic } = event;
80
- if (!value)
81
- continue;
82
- // skip explicit ignore
83
- if (value in aliases && aliases[value] === "__skip__") {
84
- continue;
85
- }
86
- // resolve alias
87
- if (value in aliases) {
88
- events.add(aliases[value]);
89
- continue;
90
- }
91
- // static
92
- if (!dynamic) {
93
- events.add(value);
94
- continue;
95
- }
96
- // already handled
97
- if (asked.has(value))
98
- continue;
99
- // ASK ONLY IF NEEDED
100
- console.log(chalk_1.default.yellow("\nDynamic event detected:"));
101
- console.log(chalk_1.default.gray(value));
102
- const { name } = await inquirer_1.default.prompt([
103
- {
104
- type: "input",
105
- name: "name",
106
- message: "Enter event name (or type 'skip'):"
107
- }
108
- ]);
109
- const normalized = name?.trim();
110
- asked.add(value);
111
- if (!normalized || normalized === "skip") {
112
- continue;
113
- }
114
- // save alias
115
- aliases[value] = normalized;
116
- console.log(chalk_1.default.green(`Saved alias: ${value} → ${normalized}`));
117
- events.add(normalized);
118
- }
119
- // free memory
120
- project.removeSourceFile(source);
28
+ const raw = await promises_1.default.readFile(file, "utf-8");
29
+ const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
30
+ engine.scanFile(virtualFile, content);
121
31
  }
122
- catch {
123
- console.log(chalk_1.default.gray(`Skipped file: ${file}`));
32
+ catch (e) {
33
+ console.log(chalk_1.default.red(`ERROR: ${file}`));
34
+ console.error(e);
124
35
  }
125
36
  }
126
- // SAVE CONFIG
127
- config.aliases = Object.fromEntries(Object.entries(aliases).sort());
128
- // MERGE
129
- const merged = new Set([
130
- ...config.events,
131
- ...events
132
- ]);
133
- config.events = [...merged].sort();
37
+ const events = engine.getAllEvents().sort();
38
+ config.events = events;
134
39
  await (0, config_1.saveConfig)(config);
135
- console.log(chalk_1.default.green(`\nFound ${config.events.length} events`));
40
+ console.log(chalk_1.default.green(`Found ${events.length} events`));
136
41
  }
@@ -6,92 +6,130 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.watch = watch;
7
7
  const chokidar_1 = __importDefault(require("chokidar"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
- const ts_morph_1 = require("ts-morph");
10
9
  const promises_1 = __importDefault(require("fs/promises"));
10
+ const fast_glob_1 = __importDefault(require("fast-glob"));
11
11
  const config_1 = require("../utils/config");
12
+ const engine_1 = require("../engine/engine");
12
13
  const router_1 = require("../utils/parsers/router");
13
14
  const vue_1 = require("../utils/parsers/vue");
14
15
  const svelte_1 = require("../utils/parsers/svelte");
15
16
  const astro_1 = require("../utils/parsers/astro");
16
- const track_1 = require("../utils/scanners/track");
17
- const function_wrappers_1 = require("../utils/scanners/function-wrappers");
18
- const component_wrappers_1 = require("../utils/scanners/component-wrappers");
17
+ // PREPROCESS (CONTENT)
18
+ function preprocess(file, content) {
19
+ const parser = (0, router_1.detectParser)(file);
20
+ if (parser === "vue")
21
+ return (0, vue_1.parseVue)(content);
22
+ if (parser === "svelte")
23
+ return (0, svelte_1.parseSvelte)(content);
24
+ if (parser === "astro")
25
+ return (0, astro_1.parseAstro)(content);
26
+ return content;
27
+ }
28
+ // VIRTUAL FILE (PATH)
29
+ function toVirtualFile(file) {
30
+ return file.endsWith(".vue")
31
+ ? file + ".tsx"
32
+ : file;
33
+ }
34
+ // WATCH
19
35
  async function watch() {
20
36
  const config = await (0, config_1.loadConfig)();
21
- if (!config) {
22
- console.log(chalk_1.default.red("Run init first"));
37
+ if (!config)
23
38
  return;
24
- }
25
39
  console.log(chalk_1.default.blue("Watching...\n"));
26
- const project = new ts_morph_1.Project();
27
- const aliases = config.aliases ?? {};
28
- // уже известные события
29
- const seen = new Set(config.events);
30
- const queue = new Set();
31
- let timer;
32
- const processQueue = async () => {
33
- const files = [...queue];
34
- queue.clear();
35
- for (const file of files) {
36
- await handle(file);
37
- }
38
- config.events = [...new Set(config.events)].sort();
39
- await (0, config_1.saveConfig)(config);
40
- };
41
- const handle = async (file) => {
40
+ const files = await (0, fast_glob_1.default)(config.sync.include, {
41
+ ignore: config.sync.exclude,
42
+ });
43
+ if (!files.length) {
44
+ console.log(chalk_1.default.yellow("No files found"));
45
+ return;
46
+ }
47
+ const engine = new engine_1.EventraEngine(process.cwd());
48
+ // INITIAL SCAN
49
+ for (const file of files) {
42
50
  try {
43
- let content = await promises_1.default.readFile(file, "utf-8");
44
- const parser = (0, router_1.detectParser)(file);
45
- if (parser === "vue")
46
- content = (0, vue_1.parseVue)(content);
47
- if (parser === "svelte")
48
- content = (0, svelte_1.parseSvelte)(content);
49
- if (parser === "astro")
50
- content = (0, astro_1.parseAstro)(content);
51
- const source = project.createSourceFile(file, content, {
52
- overwrite: true
53
- });
54
- const found = [
55
- ...(0, track_1.scanTrack)(source, config, aliases),
56
- ...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? [], aliases),
57
- ...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [], aliases)
58
- ];
59
- for (const e of found) {
60
- if (!e.value)
61
- continue;
62
- if (aliases[e.value] === "__skip__")
63
- continue;
64
- if (e.value in aliases)
65
- continue;
66
- if (seen.has(e.value))
67
- continue;
68
- seen.add(e.value);
69
- if (!e.dynamic) {
70
- config.events.push(e.value);
71
- console.log(chalk_1.default.green(`+ ${e.value}`));
72
- }
73
- else {
74
- console.log(chalk_1.default.yellow(`Dynamic: ${e.value}`));
75
- }
76
- }
77
- project.removeSourceFile(source);
51
+ const raw = await promises_1.default.readFile(file, "utf-8");
52
+ const content = preprocess(file, raw);
53
+ const virtualFile = toVirtualFile(file);
54
+ engine.scanFile(virtualFile, content);
78
55
  }
79
56
  catch {
80
57
  console.log(chalk_1.default.gray(`skip: ${file}`));
81
58
  }
59
+ }
60
+ config.events = engine.getAllEvents().sort();
61
+ await (0, config_1.saveConfig)(config);
62
+ console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
63
+ // STATE
64
+ let locked = false;
65
+ let queued = new Set();
66
+ const run = async () => {
67
+ if (locked)
68
+ return;
69
+ locked = true;
70
+ const batch = [...queued];
71
+ queued.clear();
72
+ for (const file of batch) {
73
+ try {
74
+ const raw = await promises_1.default.readFile(file, "utf-8");
75
+ const content = preprocess(file, raw);
76
+ const virtualFile = toVirtualFile(file);
77
+ engine.updateFile(virtualFile, content);
78
+ }
79
+ catch {
80
+ console.log(chalk_1.default.gray(`skip: ${file}`));
81
+ }
82
+ }
83
+ const next = engine.getAllEvents();
84
+ const added = next.filter(e => !config.events.includes(e));
85
+ const removed = config.events.filter(e => !next.includes(e));
86
+ if (added.length || removed.length) {
87
+ added.forEach(e => console.log(chalk_1.default.green(`+ ${e}`)));
88
+ removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
89
+ config.events = next.sort();
90
+ await (0, config_1.saveConfig)(config);
91
+ console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
92
+ }
93
+ locked = false;
94
+ };
95
+ const schedule = (file) => {
96
+ queued.add(file);
97
+ setTimeout(run, 50);
82
98
  };
99
+ // WATCHER
83
100
  const watcher = chokidar_1.default.watch(config.sync.include, {
84
- ignored: [
85
- ...config.sync.exclude,
86
- "**/*.log",
87
- "**/*.tmp",
88
- "**/*.swp"
89
- ],
90
- ignoreInitial: true
101
+ ignored: config.sync.exclude,
102
+ ignoreInitial: true,
103
+ awaitWriteFinish: {
104
+ stabilityThreshold: 50,
105
+ pollInterval: 10,
106
+ },
107
+ });
108
+ watcher
109
+ .on("add", (file) => {
110
+ console.log(chalk_1.default.gray(`add: ${file}`));
111
+ schedule(file);
112
+ })
113
+ .on("change", (file) => {
114
+ console.log(chalk_1.default.gray(`change: ${file}`));
115
+ schedule(file);
116
+ })
117
+ .on("unlink", async (file) => {
118
+ console.log(chalk_1.default.gray(`remove: ${file}`));
119
+ const virtualFile = toVirtualFile(file);
120
+ engine.removeFile?.(virtualFile);
121
+ const next = engine.getAllEvents();
122
+ const removed = config.events.filter(e => !next.includes(e));
123
+ if (removed.length) {
124
+ removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
125
+ config.events = next.sort();
126
+ await (0, config_1.saveConfig)(config);
127
+ }
91
128
  });
92
- watcher.on("change", (file) => {
93
- queue.add(file);
94
- clearTimeout(timer);
95
- timer = setTimeout(processQueue, 200);
129
+ // EXIT
130
+ process.on("SIGINT", async () => {
131
+ console.log(chalk_1.default.yellow("\nStopping watcher..."));
132
+ await watcher.close();
133
+ process.exit(0);
96
134
  });
97
135
  }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveFunctionFromCall = resolveFunctionFromCall;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ function resolveFunctionFromCall(expr, checker) {
9
+ let symbol = checker.getSymbolAtLocation(expr);
10
+ if (!symbol && typescript_1.default.isPropertyAccessExpression(expr)) {
11
+ symbol = checker.getSymbolAtLocation(expr.name);
12
+ }
13
+ if (!symbol)
14
+ return null;
15
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
16
+ symbol = checker.getAliasedSymbol(symbol);
17
+ }
18
+ for (const decl of symbol.getDeclarations() ?? []) {
19
+ if (typescript_1.default.isFunctionDeclaration(decl) ||
20
+ typescript_1.default.isMethodDeclaration(decl) ||
21
+ typescript_1.default.isFunctionExpression(decl) ||
22
+ typescript_1.default.isArrowFunction(decl)) {
23
+ return decl;
24
+ }
25
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
26
+ const init = decl.initializer;
27
+ if (typescript_1.default.isArrowFunction(init) ||
28
+ typescript_1.default.isFunctionExpression(init)) {
29
+ return init;
30
+ }
31
+ }
32
+ }
33
+ return null;
34
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventraEngine = void 0;
4
+ const languageService_1 = require("./languageService");
5
+ const scanner_1 = require("./scanner");
6
+ const graph_1 = require("./graph");
7
+ const hash_1 = require("./hash");
8
+ class EventraEngine {
9
+ ts;
10
+ graph = new graph_1.DependencyGraph();
11
+ fileEvents = new Map();
12
+ fileHash = new Map();
13
+ constructor(root) {
14
+ this.ts = new languageService_1.TSService(root);
15
+ }
16
+ scanFile(file, content) {
17
+ const h = (0, hash_1.hash)(content);
18
+ if (this.fileHash.get(file) === h) {
19
+ return this.fileEvents.get(file) ?? new Set();
20
+ }
21
+ this.fileHash.set(file, h);
22
+ this.ts.updateFile(file, content);
23
+ const source = this.ts.getSourceFile(file);
24
+ const checker = this.ts.getChecker();
25
+ if (!source || !checker)
26
+ return new Set();
27
+ this.graph.update(file, source);
28
+ const events = (0, scanner_1.scanSource)(source, checker);
29
+ this.fileEvents.set(file, events);
30
+ return events;
31
+ }
32
+ updateFile(file, content) {
33
+ return this.scanFile(file, content);
34
+ }
35
+ getAllEvents() {
36
+ const all = new Set();
37
+ for (const set of this.fileEvents.values()) {
38
+ set.forEach(e => all.add(e));
39
+ }
40
+ return [...all];
41
+ }
42
+ removeFile(file) {
43
+ this.fileEvents.delete(file);
44
+ this.fileHash.delete(file);
45
+ }
46
+ }
47
+ exports.EventraEngine = EventraEngine;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DependencyGraph = void 0;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ class DependencyGraph {
9
+ reverse = new Map();
10
+ update(file, source) {
11
+ for (const stmt of source.statements) {
12
+ if (!typescript_1.default.isImportDeclaration(stmt))
13
+ continue;
14
+ const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
15
+ if (!this.reverse.has(module)) {
16
+ this.reverse.set(module, new Set());
17
+ }
18
+ this.reverse.get(module).add(file);
19
+ }
20
+ }
21
+ getDependents(file) {
22
+ return [...(this.reverse.get(file) ?? [])];
23
+ }
24
+ }
25
+ exports.DependencyGraph = DependencyGraph;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hash = hash;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ function hash(content) {
9
+ return crypto_1.default.createHash("md5").update(content).digest("hex");
10
+ }