@eventra_dev/eventra-cli 0.1.25 → 0.1.27
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.
- package/README.md +62 -50
- package/dist/commands/watch.js +41 -23
- package/dist/engine/callResolver.js +52 -4
- package/dist/engine/engine.js +63 -31
- package/dist/engine/exportResolver.js +16 -4
- package/dist/engine/functionAnalyzer.js +66 -13
- package/dist/engine/graph.js +40 -4
- package/dist/engine/languageService.js +83 -20
- package/dist/engine/resolver.js +29 -21
- package/dist/engine/scanner.js +57 -46
- package/dist/engine/trackingDetector.js +68 -0
- package/dist/utils/processFile.js +0 -9
- package/dist/utils/universal.js +25 -87
- package/package.json +1 -1
- package/src/commands/watch.ts +45 -25
- package/src/engine/callResolver.ts +77 -6
- package/src/engine/engine.ts +77 -32
- package/src/engine/exportResolver.ts +22 -5
- package/src/engine/functionAnalyzer.ts +76 -16
- package/src/engine/graph.ts +45 -6
- package/src/engine/languageService.ts +100 -29
- package/src/engine/resolver.ts +36 -27
- package/src/engine/scanner.ts +72 -50
- package/src/engine/trackingDetector.ts +94 -0
- package/src/types.ts +0 -3
- package/src/utils/processFile.ts +0 -9
- package/src/utils/universal.ts +26 -102
package/README.md
CHANGED
|
@@ -9,23 +9,29 @@
|
|
|
9
9
|
[](https://www.typescriptlang.org/)
|
|
10
10
|
[]()
|
|
11
11
|
|
|
12
|
-
Eventra CLI automatically discovers
|
|
12
|
+
Eventra CLI automatically discovers and tracks event usage across your codebase — even through abstractions like wrappers, variables, and cross-file calls.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## Overview
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Eventra scans your codebase using a TypeScript-powered engine and extracts event usage — even through abstractions like wrappers, variables, and cross-file calls.
|
|
19
|
+
|
|
20
|
+
It requires no runtime instrumentation and fits naturally into CI/CD workflows.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
21
25
|
|
|
22
26
|
```bash
|
|
27
|
+
npm install -D @eventra_dev/eventra-cli
|
|
28
|
+
# or
|
|
23
29
|
pnpm add -D @eventra_dev/eventra-cli
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
---
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
## Quick Start
|
|
29
35
|
|
|
30
36
|
```bash
|
|
31
37
|
eventra init
|
|
@@ -36,22 +42,43 @@ eventra send
|
|
|
36
42
|
|
|
37
43
|
---
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
## How it works
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
1. Parses your project using the TypeScript AST
|
|
48
|
+
2. Resolves function calls, imports, and dependencies
|
|
49
|
+
3. Tracks event propagation through variables and wrappers
|
|
50
|
+
4. Extracts event names and aggregates them
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
No runtime required. No SDK needed.
|
|
44
53
|
|
|
45
54
|
---
|
|
46
55
|
|
|
47
|
-
##
|
|
56
|
+
## Why Eventra
|
|
57
|
+
|
|
58
|
+
- Prevent missing or inconsistent event tracking
|
|
59
|
+
- Keep your analytics schema in sync with code
|
|
60
|
+
- Detect dead or unused events
|
|
61
|
+
- Integrate validation into CI pipelines
|
|
62
|
+
|
|
63
|
+
---
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
## Commands
|
|
50
66
|
|
|
51
|
-
###
|
|
67
|
+
### `eventra init`
|
|
68
|
+
Creates `eventra.json` configuration file.
|
|
69
|
+
|
|
70
|
+
---
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
### `eventra sync`
|
|
73
|
+
Scans your project and updates the list of detected events.
|
|
74
|
+
|
|
75
|
+
#### Supports
|
|
76
|
+
|
|
77
|
+
- Direct calls
|
|
78
|
+
```ts
|
|
79
|
+
track("event")
|
|
80
|
+
analytics.track("event")
|
|
81
|
+
```
|
|
55
82
|
|
|
56
83
|
- Variables
|
|
57
84
|
```ts
|
|
@@ -74,7 +101,7 @@ track(`click_${type}`)
|
|
|
74
101
|
track(condition ? "a" : "b")
|
|
75
102
|
```
|
|
76
103
|
|
|
77
|
-
- Object
|
|
104
|
+
- Object payloads
|
|
78
105
|
```ts
|
|
79
106
|
track({ event: "click" })
|
|
80
107
|
```
|
|
@@ -88,36 +115,27 @@ const trackFeature = (name: string) => {
|
|
|
88
115
|
trackFeature("click")
|
|
89
116
|
```
|
|
90
117
|
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
trackFeature("test", "click")
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
- Cross-file tracking
|
|
97
|
-
|
|
98
|
-
- Vue / Svelte / Astro support
|
|
118
|
+
- Cross-file event resolution
|
|
119
|
+
- Framework templates (Vue, Svelte, Astro)
|
|
99
120
|
|
|
100
121
|
---
|
|
101
122
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Validate events without modifying config.
|
|
123
|
+
### `eventra check`
|
|
124
|
+
Validates detected events against config.
|
|
105
125
|
|
|
106
126
|
```bash
|
|
107
127
|
eventra check
|
|
108
128
|
```
|
|
109
129
|
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
Detects:
|
|
112
131
|
- New events
|
|
113
132
|
- Removed events
|
|
114
|
-
-
|
|
133
|
+
- Inconsistent tracking usage
|
|
115
134
|
|
|
116
135
|
---
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Auto-fix mode.
|
|
137
|
+
### `eventra check --fix`
|
|
138
|
+
Automatically updates config to match detected events.
|
|
121
139
|
|
|
122
140
|
```bash
|
|
123
141
|
eventra check --fix
|
|
@@ -125,9 +143,8 @@ eventra check --fix
|
|
|
125
143
|
|
|
126
144
|
---
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Real-time event detection.
|
|
146
|
+
### `eventra watch`
|
|
147
|
+
Runs in watch mode and updates events in real time.
|
|
131
148
|
|
|
132
149
|
```bash
|
|
133
150
|
eventra watch
|
|
@@ -135,9 +152,8 @@ eventra watch
|
|
|
135
152
|
|
|
136
153
|
---
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Send events to backend.
|
|
155
|
+
### `eventra send`
|
|
156
|
+
Sends events to Eventra backend.
|
|
141
157
|
|
|
142
158
|
```bash
|
|
143
159
|
eventra send
|
|
@@ -145,11 +161,12 @@ eventra send
|
|
|
145
161
|
|
|
146
162
|
---
|
|
147
163
|
|
|
148
|
-
|
|
164
|
+
## Configuration
|
|
149
165
|
|
|
150
166
|
```json
|
|
151
167
|
{
|
|
152
168
|
"apiKey": "",
|
|
169
|
+
"endpoint": "",
|
|
153
170
|
"events": [],
|
|
154
171
|
"wrappers": [],
|
|
155
172
|
"functionWrappers": [],
|
|
@@ -163,25 +180,20 @@ eventra send
|
|
|
163
180
|
|
|
164
181
|
---
|
|
165
182
|
|
|
166
|
-
|
|
183
|
+
## Supported Environments
|
|
184
|
+
|
|
185
|
+
Eventra works with **any JavaScript or TypeScript codebase**.
|
|
167
186
|
|
|
168
|
-
|
|
169
|
-
- Vue
|
|
170
|
-
- Svelte
|
|
171
|
-
- Astro
|
|
172
|
-
- Node.js
|
|
173
|
-
- Express
|
|
174
|
-
- NestJS
|
|
175
|
-
- Fastify
|
|
187
|
+
No framework limitations.
|
|
176
188
|
|
|
177
189
|
---
|
|
178
190
|
|
|
179
|
-
|
|
191
|
+
## Requirements
|
|
180
192
|
|
|
181
193
|
- Node.js 18+
|
|
182
194
|
|
|
183
195
|
---
|
|
184
196
|
|
|
185
|
-
|
|
197
|
+
## License
|
|
186
198
|
|
|
187
199
|
MIT
|
package/dist/commands/watch.js
CHANGED
|
@@ -13,6 +13,9 @@ 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
|
+
}
|
|
16
19
|
async function watch() {
|
|
17
20
|
const config = await (0, config_1.loadConfig)();
|
|
18
21
|
if (!config)
|
|
@@ -29,28 +32,29 @@ async function watch() {
|
|
|
29
32
|
// COLLECT + CACHE
|
|
30
33
|
for (const file of files) {
|
|
31
34
|
try {
|
|
32
|
-
const
|
|
35
|
+
const abs = normalize(file);
|
|
36
|
+
const raw = await promises_1.default.readFile(abs, "utf-8");
|
|
33
37
|
if (raw.length > 2_000_000)
|
|
34
|
-
continue;
|
|
35
|
-
const parsed = (0, processFile_1.processFile)(
|
|
36
|
-
cache.set(
|
|
37
|
-
toScan.add(
|
|
38
|
+
continue;
|
|
39
|
+
const parsed = (0, processFile_1.processFile)(abs, raw);
|
|
40
|
+
cache.set(abs, parsed);
|
|
41
|
+
toScan.add(abs);
|
|
38
42
|
for (const dep of parsed.deps) {
|
|
39
|
-
|
|
40
|
-
toScan.add(absDep);
|
|
43
|
+
toScan.add(normalize(dep));
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
catch {
|
|
44
47
|
console.log(chalk_1.default.gray(`skip: ${file}`));
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
|
-
// PRELOAD
|
|
50
|
+
// PRELOAD
|
|
48
51
|
for (const file of toScan) {
|
|
49
52
|
try {
|
|
50
53
|
let parsed = cache.get(file);
|
|
51
54
|
if (!parsed) {
|
|
52
55
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
53
56
|
parsed = (0, processFile_1.processFile)(file, raw);
|
|
57
|
+
cache.set(file, parsed);
|
|
54
58
|
}
|
|
55
59
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
|
|
56
60
|
engine.preloadFile?.(virtual, parsed.content);
|
|
@@ -68,7 +72,7 @@ async function watch() {
|
|
|
68
72
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
|
|
69
73
|
engine.scanFile(virtual, parsed.content, config);
|
|
70
74
|
for (const dep of parsed.deps) {
|
|
71
|
-
watchedDeps.add(
|
|
75
|
+
watchedDeps.add(normalize(dep));
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
catch { }
|
|
@@ -94,19 +98,33 @@ async function watch() {
|
|
|
94
98
|
const parsed = (0, processFile_1.processFile)(item.real, raw);
|
|
95
99
|
cache.set(item.real, parsed);
|
|
96
100
|
engine.updateFile(item.virtual, parsed.content, config);
|
|
97
|
-
//
|
|
101
|
+
// DEPENDENCY DIFF
|
|
102
|
+
const nextDeps = new Set();
|
|
98
103
|
for (const dep of parsed.deps) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
nextDeps.add(normalize(dep));
|
|
105
|
+
}
|
|
106
|
+
// add new deps
|
|
107
|
+
for (const dep of nextDeps) {
|
|
108
|
+
if (!watchedDeps.has(dep)) {
|
|
109
|
+
watcher.add(dep);
|
|
110
|
+
watchedDeps.add(dep);
|
|
103
111
|
}
|
|
112
|
+
}
|
|
113
|
+
// remove old deps
|
|
114
|
+
for (const dep of [...watchedDeps]) {
|
|
115
|
+
if (!nextDeps.has(dep)) {
|
|
116
|
+
watcher.unwatch(dep);
|
|
117
|
+
watchedDeps.delete(dep);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// preload deps
|
|
121
|
+
for (const dep of nextDeps) {
|
|
104
122
|
try {
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
cache.set(
|
|
108
|
-
const
|
|
109
|
-
engine.updateFile(
|
|
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);
|
|
110
128
|
}
|
|
111
129
|
catch { }
|
|
112
130
|
}
|
|
@@ -130,13 +148,13 @@ async function watch() {
|
|
|
130
148
|
};
|
|
131
149
|
// SCHEDULER
|
|
132
150
|
const schedule = (file) => {
|
|
133
|
-
const abs =
|
|
151
|
+
const abs = normalize(file);
|
|
134
152
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
|
|
135
153
|
console.log(chalk_1.default.gray(`change: ${file}`));
|
|
136
154
|
queued.set(abs, { real: abs, virtual });
|
|
137
155
|
if (timer)
|
|
138
156
|
clearTimeout(timer);
|
|
139
|
-
timer = setTimeout(run,
|
|
157
|
+
timer = setTimeout(run, 200);
|
|
140
158
|
};
|
|
141
159
|
// WATCHER
|
|
142
160
|
const watcher = chokidar_1.default.watch(config.sync.include, {
|
|
@@ -148,7 +166,7 @@ async function watch() {
|
|
|
148
166
|
.on("add", schedule)
|
|
149
167
|
.on("change", schedule)
|
|
150
168
|
.on("unlink", async (file) => {
|
|
151
|
-
const abs =
|
|
169
|
+
const abs = normalize(file);
|
|
152
170
|
console.log(chalk_1.default.gray(`remove: ${file}`));
|
|
153
171
|
const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
|
|
154
172
|
engine.removeFile(virtual, config);
|
|
@@ -162,7 +180,7 @@ async function watch() {
|
|
|
162
180
|
.on("error", (err) => {
|
|
163
181
|
console.error(chalk_1.default.red("Watcher error:"), err);
|
|
164
182
|
});
|
|
165
|
-
// WATCH DEPS
|
|
183
|
+
// WATCH DEPS INIT
|
|
166
184
|
for (const dep of watchedDeps) {
|
|
167
185
|
watcher.add(dep);
|
|
168
186
|
}
|
|
@@ -23,15 +23,37 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
23
23
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
24
24
|
return resolveFromSymbol(aliased, checker, visited, depth + 1);
|
|
25
25
|
}
|
|
26
|
+
// re-export support
|
|
27
|
+
if (symbol.exports) {
|
|
28
|
+
for (const exp of symbol.exports.values()) {
|
|
29
|
+
const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
|
|
30
|
+
if (resolved)
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
26
34
|
const decls = symbol.getDeclarations() ?? [];
|
|
27
35
|
for (const decl of decls) {
|
|
36
|
+
// direct function
|
|
28
37
|
if (isFunctionLike(decl)) {
|
|
29
38
|
return decl;
|
|
30
39
|
}
|
|
31
|
-
//
|
|
40
|
+
// VARIABLE → FUNCTION
|
|
41
|
+
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
42
|
+
if (isFunctionLike(decl.initializer)) {
|
|
43
|
+
return decl.initializer;
|
|
44
|
+
}
|
|
45
|
+
const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
|
|
46
|
+
if (innerSymbol && innerSymbol !== symbol) {
|
|
47
|
+
const resolved = resolveFromSymbol(innerSymbol, checker, visited, depth + 1);
|
|
48
|
+
if (resolved)
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// MethodSignature (interface / type)
|
|
32
53
|
if (typescript_1.default.isMethodSignature(decl)) {
|
|
33
54
|
const parent = decl.parent;
|
|
34
|
-
if (typescript_1.default.isInterfaceDeclaration(parent) ||
|
|
55
|
+
if (typescript_1.default.isInterfaceDeclaration(parent) ||
|
|
56
|
+
typescript_1.default.isTypeLiteralNode(parent)) {
|
|
35
57
|
const name = decl.name.getText();
|
|
36
58
|
const type = checker.getTypeAtLocation(parent);
|
|
37
59
|
const props = type.getProperties();
|
|
@@ -44,7 +66,7 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
44
66
|
}
|
|
45
67
|
}
|
|
46
68
|
}
|
|
47
|
-
//
|
|
69
|
+
// fallback recursion
|
|
48
70
|
const s = checker.getSymbolAtLocation(decl);
|
|
49
71
|
if (s && s !== symbol) {
|
|
50
72
|
const resolved = resolveFromSymbol(s, checker, visited, depth + 1);
|
|
@@ -55,6 +77,28 @@ function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
|
|
|
55
77
|
return null;
|
|
56
78
|
}
|
|
57
79
|
function resolveFunctionFromCall(expr, checker) {
|
|
80
|
+
// tracker.track
|
|
81
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
82
|
+
let symbol = checker.getSymbolAtLocation(expr.name);
|
|
83
|
+
if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
|
|
84
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
85
|
+
}
|
|
86
|
+
if (symbol) {
|
|
87
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
88
|
+
if (resolved)
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// tracker["track"]
|
|
93
|
+
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
94
|
+
const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
|
|
95
|
+
if (symbol) {
|
|
96
|
+
const resolved = resolveFromSymbol(symbol, checker);
|
|
97
|
+
if (resolved)
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// normal call signatures
|
|
58
102
|
const type = checker.getTypeAtLocation(expr);
|
|
59
103
|
const signatures = type.getCallSignatures();
|
|
60
104
|
for (const sig of signatures) {
|
|
@@ -64,7 +108,6 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
64
108
|
if (isFunctionLike(decl)) {
|
|
65
109
|
return decl;
|
|
66
110
|
}
|
|
67
|
-
// interface method
|
|
68
111
|
if (typescript_1.default.isMethodSignature(decl)) {
|
|
69
112
|
const symbol = checker.getSymbolAtLocation(decl.name);
|
|
70
113
|
if (symbol) {
|
|
@@ -74,5 +117,10 @@ function resolveFunctionFromCall(expr, checker) {
|
|
|
74
117
|
}
|
|
75
118
|
}
|
|
76
119
|
}
|
|
120
|
+
// fallback
|
|
121
|
+
const symbol = checker.getSymbolAtLocation(expr);
|
|
122
|
+
if (symbol) {
|
|
123
|
+
return resolveFromSymbol(symbol, checker);
|
|
124
|
+
}
|
|
77
125
|
return null;
|
|
78
126
|
}
|
package/dist/engine/engine.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.EventraEngine = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
4
9
|
const languageService_1 = require("./languageService");
|
|
5
10
|
const scanner_1 = require("./scanner");
|
|
6
11
|
const graph_1 = require("./graph");
|
|
@@ -16,31 +21,40 @@ class EventraEngine {
|
|
|
16
21
|
const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
|
|
17
22
|
this.graph = new graph_1.DependencyGraph(baseUrl, paths);
|
|
18
23
|
}
|
|
24
|
+
normalize(file) {
|
|
25
|
+
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
26
|
+
}
|
|
27
|
+
empty() {
|
|
28
|
+
return {
|
|
29
|
+
events: new Set(),
|
|
30
|
+
detectedFunctionWrappers: new Set(),
|
|
31
|
+
detectedComponentWrappers: new Map(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// PRELOAD
|
|
19
35
|
preloadFile(file, content) {
|
|
36
|
+
file = this.normalize(file);
|
|
20
37
|
this.ts.updateFile(file, content);
|
|
38
|
+
const source = this.ts.getSourceFile(file);
|
|
39
|
+
if (source) {
|
|
40
|
+
this.graph.update(file, source);
|
|
41
|
+
}
|
|
21
42
|
}
|
|
22
43
|
// SCAN FILE
|
|
23
44
|
scanFile(file, content, config) {
|
|
45
|
+
file = this.normalize(file);
|
|
24
46
|
const h = (0, hash_1.hash)(content);
|
|
25
|
-
// skip unchanged
|
|
26
47
|
if (this.fileHash.get(file) === h) {
|
|
27
|
-
return
|
|
28
|
-
events: new Set(),
|
|
29
|
-
detectedFunctionWrappers: new Set(),
|
|
30
|
-
detectedComponentWrappers: new Map(),
|
|
31
|
-
});
|
|
48
|
+
return this.fileResults.get(file) ?? this.empty();
|
|
32
49
|
}
|
|
33
50
|
this.fileHash.set(file, h);
|
|
34
51
|
this.ts.updateFile(file, content);
|
|
35
52
|
const source = this.ts.getSourceFile(file);
|
|
36
53
|
const checker = this.ts.getChecker();
|
|
37
54
|
if (!source || !checker) {
|
|
38
|
-
return
|
|
39
|
-
events: new Set(),
|
|
40
|
-
detectedFunctionWrappers: new Set(),
|
|
41
|
-
detectedComponentWrappers: new Map(),
|
|
42
|
-
};
|
|
55
|
+
return this.empty();
|
|
43
56
|
}
|
|
57
|
+
// обновляем graph
|
|
44
58
|
this.graph.update(file, source);
|
|
45
59
|
const res = (0, scanner_1.scanSource)(source, checker, config);
|
|
46
60
|
this.fileResults.set(file, res);
|
|
@@ -48,26 +62,35 @@ class EventraEngine {
|
|
|
48
62
|
}
|
|
49
63
|
// UPDATE FILE (incremental)
|
|
50
64
|
updateFile(file, content, config) {
|
|
65
|
+
file = this.normalize(file);
|
|
51
66
|
const res = this.scanFile(file, content, config);
|
|
52
67
|
const dependents = this.graph.getAllDependentsDeep(file);
|
|
53
68
|
for (const dep of dependents) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const depContent = this.ts.getFileContent(dep);
|
|
57
|
-
if (!depContent)
|
|
69
|
+
const abs = this.normalize(dep);
|
|
70
|
+
if ((0, boundary_1.isExternalFile)(abs))
|
|
58
71
|
continue;
|
|
72
|
+
let depContent = this.ts.getFileContent(abs);
|
|
73
|
+
if (!depContent) {
|
|
74
|
+
try {
|
|
75
|
+
depContent = fs_1.default.readFileSync(abs, "utf-8");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
59
81
|
const h = (0, hash_1.hash)(depContent);
|
|
60
|
-
if (this.fileHash.get(
|
|
82
|
+
if (this.fileHash.get(abs) === h) {
|
|
61
83
|
continue;
|
|
62
|
-
|
|
63
|
-
this.
|
|
64
|
-
|
|
84
|
+
}
|
|
85
|
+
this.fileHash.set(abs, h);
|
|
86
|
+
this.ts.updateFile(abs, depContent);
|
|
87
|
+
const source = this.ts.getSourceFile(abs);
|
|
65
88
|
const checker = this.ts.getChecker();
|
|
66
89
|
if (!source || !checker)
|
|
67
90
|
continue;
|
|
68
|
-
this.graph.update(
|
|
91
|
+
this.graph.update(abs, source);
|
|
69
92
|
const r = (0, scanner_1.scanSource)(source, checker, config);
|
|
70
|
-
this.fileResults.set(
|
|
93
|
+
this.fileResults.set(abs, r);
|
|
71
94
|
}
|
|
72
95
|
return res;
|
|
73
96
|
}
|
|
@@ -81,33 +104,42 @@ class EventraEngine {
|
|
|
81
104
|
}
|
|
82
105
|
// REMOVE FILE
|
|
83
106
|
removeFile(file, config) {
|
|
107
|
+
file = this.normalize(file);
|
|
108
|
+
const dependents = this.graph.getAllDependentsDeep(file);
|
|
84
109
|
this.ts.removeFile(file);
|
|
85
110
|
this.fileResults.delete(file);
|
|
86
111
|
this.fileHash.delete(file);
|
|
87
112
|
this.graph.remove(file);
|
|
88
|
-
const dependents = this.graph.getAllDependentsDeep(file);
|
|
89
113
|
for (const dep of dependents) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const content = this.ts.getFileContent(dep);
|
|
93
|
-
if (!content)
|
|
114
|
+
const abs = this.normalize(dep);
|
|
115
|
+
if ((0, boundary_1.isExternalFile)(abs))
|
|
94
116
|
continue;
|
|
117
|
+
let content = this.ts.getFileContent(abs);
|
|
118
|
+
if (!content) {
|
|
119
|
+
try {
|
|
120
|
+
content = fs_1.default.readFileSync(abs, "utf-8");
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
95
126
|
const h = (0, hash_1.hash)(content);
|
|
96
|
-
if (this.fileHash.get(
|
|
127
|
+
if (this.fileHash.get(abs) === h)
|
|
97
128
|
continue;
|
|
98
|
-
this.fileHash.set(
|
|
99
|
-
this.ts.updateFile(
|
|
100
|
-
const source = this.ts.getSourceFile(
|
|
129
|
+
this.fileHash.set(abs, h);
|
|
130
|
+
this.ts.updateFile(abs, content);
|
|
131
|
+
const source = this.ts.getSourceFile(abs);
|
|
101
132
|
const checker = this.ts.getChecker();
|
|
102
133
|
if (!source || !checker)
|
|
103
134
|
continue;
|
|
135
|
+
this.graph.update(abs, source);
|
|
104
136
|
const r = (0, scanner_1.scanSource)(source, checker, config ?? {
|
|
105
137
|
events: [],
|
|
106
138
|
wrappers: [],
|
|
107
139
|
functionWrappers: [],
|
|
108
140
|
sync: { include: [], exclude: [] },
|
|
109
141
|
});
|
|
110
|
-
this.fileResults.set(
|
|
142
|
+
this.fileResults.set(abs, r);
|
|
111
143
|
}
|
|
112
144
|
}
|
|
113
145
|
}
|
|
@@ -5,21 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveExportedSymbol = resolveExportedSymbol;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
function isRealDeclaration(node) {
|
|
9
|
+
return (typescript_1.default.isFunctionDeclaration(node) ||
|
|
10
|
+
typescript_1.default.isVariableDeclaration(node) ||
|
|
11
|
+
typescript_1.default.isClassDeclaration(node));
|
|
12
|
+
}
|
|
8
13
|
function resolveExportedSymbol(symbol, checker, visited = new Set()) {
|
|
9
14
|
if (visited.has(symbol))
|
|
10
15
|
return null;
|
|
11
16
|
visited.add(symbol);
|
|
17
|
+
// unwrap alias
|
|
12
18
|
if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
|
|
13
19
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
14
20
|
return resolveExportedSymbol(aliased, checker, visited);
|
|
15
21
|
}
|
|
16
|
-
|
|
22
|
+
// export symbol
|
|
23
|
+
const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
|
|
24
|
+
if (exportSymbol && exportSymbol !== symbol) {
|
|
25
|
+
return resolveExportedSymbol(exportSymbol, checker, visited);
|
|
26
|
+
}
|
|
27
|
+
const decls = symbol.getDeclarations() ?? [];
|
|
28
|
+
if (decls.some(isRealDeclaration)) {
|
|
17
29
|
return symbol;
|
|
18
30
|
}
|
|
19
|
-
|
|
20
|
-
for (const decl of
|
|
31
|
+
// exports (re-export)
|
|
32
|
+
for (const decl of decls) {
|
|
21
33
|
const source = decl.getSourceFile();
|
|
22
|
-
const moduleSymbol =
|
|
34
|
+
const moduleSymbol = source.symbol;
|
|
23
35
|
if (!moduleSymbol)
|
|
24
36
|
continue;
|
|
25
37
|
const exports = checker.getExportsOfModule(moduleSymbol);
|