@eventra_dev/eventra-cli 0.1.2 → 0.1.4
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 +48 -73
- package/dist/commands/check.js +18 -7
- package/dist/commands/send.js +7 -1
- package/dist/commands/sync.js +5 -3
- package/dist/commands/watch.js +13 -55
- package/dist/engine/callResolver.js +9 -21
- package/dist/engine/engine.js +12 -1
- package/dist/engine/functionAnalyzer.js +45 -0
- package/dist/engine/graph.js +75 -7
- package/dist/engine/languageService.js +24 -18
- package/dist/engine/resolver.js +37 -4
- package/dist/engine/scanner.js +67 -17
- package/dist/utils/config.js +7 -2
- package/dist/utils/parsers/astro.js +8 -1
- package/dist/utils/parsers/router.js +1 -0
- package/dist/utils/parsers/svelte.js +4 -1
- package/dist/utils/parsers/vue.js +13 -4
- package/dist/utils/processFile.js +3 -1
- package/package.json +1 -1
- package/src/commands/check.ts +21 -7
- package/src/commands/send.ts +8 -2
- package/src/commands/sync.ts +6 -3
- package/src/commands/watch.ts +14 -60
- package/src/engine/callResolver.ts +9 -26
- package/src/engine/engine.ts +15 -3
- package/src/engine/functionAnalyzer.ts +51 -0
- package/src/engine/graph.ts +93 -6
- package/src/engine/languageService.ts +47 -20
- package/src/engine/resolver.ts +51 -4
- package/src/engine/scanner.ts +88 -21
- package/src/utils/config.ts +15 -33
- package/src/utils/parsers/astro.ts +11 -4
- package/src/utils/parsers/router.ts +5 -10
- package/src/utils/parsers/svelte.ts +6 -4
- package/src/utils/parsers/vue.ts +15 -5
- package/src/utils/processFile.ts +6 -3
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
<p align="center">
|
|
3
2
|
<img src="./assets/eventra-icon-animated.svg" width="120">
|
|
4
3
|
</p>
|
|
@@ -49,13 +48,54 @@ Creates `eventra.json` configuration file.
|
|
|
49
48
|
|
|
50
49
|
Scans your project and discovers events automatically.
|
|
51
50
|
|
|
52
|
-
Supports:
|
|
51
|
+
### Supports:
|
|
53
52
|
|
|
54
53
|
- track("event")
|
|
55
54
|
- analytics.track("event")
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
55
|
+
|
|
56
|
+
- Variables
|
|
57
|
+
```ts
|
|
58
|
+
const EVENT = "click"
|
|
59
|
+
track(EVENT)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- Enums
|
|
63
|
+
```ts
|
|
64
|
+
track(EVENTS.CLICK)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- Template strings
|
|
68
|
+
```ts
|
|
69
|
+
track(`click_${type}`)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- Conditionals
|
|
73
|
+
```ts
|
|
74
|
+
track(condition ? "a" : "b")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Object format
|
|
78
|
+
```ts
|
|
79
|
+
track({ event: "click" })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Function wrappers
|
|
83
|
+
```ts
|
|
84
|
+
const trackFeature = (name: string) => {
|
|
85
|
+
track(name)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
trackFeature("click")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- Multi-argument wrappers
|
|
92
|
+
```ts
|
|
93
|
+
trackFeature("test", "click")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- Cross-file tracking
|
|
97
|
+
|
|
98
|
+
- Vue / Svelte / Astro support
|
|
59
99
|
|
|
60
100
|
---
|
|
61
101
|
|
|
@@ -70,34 +110,19 @@ eventra check
|
|
|
70
110
|
### Detects:
|
|
71
111
|
|
|
72
112
|
- New events
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
### Output example:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
New events:
|
|
79
|
-
+ user_click (src/Button.tsx:42)
|
|
80
|
-
|
|
81
|
-
Dynamic:
|
|
82
|
-
- eventName (src/App.tsx:10)
|
|
83
|
-
```
|
|
113
|
+
- Removed events
|
|
114
|
+
- Dynamic values
|
|
84
115
|
|
|
85
116
|
---
|
|
86
117
|
|
|
87
118
|
## eventra check --fix
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
Auto-fix mode.
|
|
90
121
|
|
|
91
122
|
```bash
|
|
92
123
|
eventra check --fix
|
|
93
124
|
```
|
|
94
125
|
|
|
95
|
-
### What it does:
|
|
96
|
-
|
|
97
|
-
- Adds new events to config
|
|
98
|
-
- Resolves dynamic events via alias
|
|
99
|
-
- Allows skipping dynamic values
|
|
100
|
-
|
|
101
126
|
---
|
|
102
127
|
|
|
103
128
|
## eventra watch
|
|
@@ -108,13 +133,6 @@ Real-time event detection.
|
|
|
108
133
|
eventra watch
|
|
109
134
|
```
|
|
110
135
|
|
|
111
|
-
### Features:
|
|
112
|
-
|
|
113
|
-
- Detects new events instantly
|
|
114
|
-
- Debounced updates
|
|
115
|
-
- No duplicates
|
|
116
|
-
- Ignores temp files
|
|
117
|
-
|
|
118
136
|
---
|
|
119
137
|
|
|
120
138
|
## eventra send
|
|
@@ -129,8 +147,6 @@ eventra send
|
|
|
129
147
|
|
|
130
148
|
# Configuration
|
|
131
149
|
|
|
132
|
-
Example `eventra.json`:
|
|
133
|
-
|
|
134
150
|
```json
|
|
135
151
|
{
|
|
136
152
|
"apiKey": "",
|
|
@@ -147,53 +163,12 @@ Example `eventra.json`:
|
|
|
147
163
|
|
|
148
164
|
---
|
|
149
165
|
|
|
150
|
-
# Key Concepts
|
|
151
|
-
|
|
152
|
-
### Aliases
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
const EVENT = "user_click";
|
|
156
|
-
track(EVENT);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Auto-resolved → `user_click`
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
### Dynamic events
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
track(eventName);
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Must be:
|
|
170
|
-
|
|
171
|
-
- aliased
|
|
172
|
-
- or skipped (`__skip__`)
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
### File tracking
|
|
177
|
-
|
|
178
|
-
All events include:
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
event_name (file:line)
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
166
|
# Supported Frameworks
|
|
187
167
|
|
|
188
|
-
Frontend:
|
|
189
|
-
|
|
190
168
|
- React
|
|
191
169
|
- Vue
|
|
192
170
|
- Svelte
|
|
193
171
|
- Astro
|
|
194
|
-
|
|
195
|
-
Backend:
|
|
196
|
-
|
|
197
172
|
- Node.js
|
|
198
173
|
- Express
|
|
199
174
|
- NestJS
|
package/dist/commands/check.js
CHANGED
|
@@ -27,26 +27,37 @@ async function check({ fix = false }) {
|
|
|
27
27
|
const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
|
|
28
28
|
engine.scanFile(virtualFile, content);
|
|
29
29
|
}
|
|
30
|
-
catch {
|
|
31
|
-
console.log(chalk_1.default.
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.log(chalk_1.default.yellow(`Skipped: ${file}`));
|
|
32
|
+
if (err instanceof Error) {
|
|
33
|
+
console.log(chalk_1.default.gray(err.message));
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
const found = new Set(engine.getAllEvents());
|
|
35
38
|
const known = new Set(config.events ?? []);
|
|
36
39
|
const newEvents = [...found].filter(e => !known.has(e));
|
|
40
|
+
const removed = [...known].filter(e => !found.has(e));
|
|
37
41
|
if (fix) {
|
|
42
|
+
const merged = new Set(config.events ?? []);
|
|
38
43
|
newEvents.forEach(e => {
|
|
39
|
-
|
|
44
|
+
merged.add(e);
|
|
40
45
|
console.log(chalk_1.default.green(`+ ${e}`));
|
|
41
46
|
});
|
|
42
|
-
config.events = [...
|
|
47
|
+
config.events = [...merged].sort();
|
|
43
48
|
await (0, config_1.saveConfig)(config);
|
|
44
49
|
console.log(chalk_1.default.green("\nFixed"));
|
|
45
50
|
return;
|
|
46
51
|
}
|
|
47
|
-
if (newEvents.length) {
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
if (newEvents.length || removed.length) {
|
|
53
|
+
if (newEvents.length) {
|
|
54
|
+
console.log(chalk_1.default.red("\nNew events:"));
|
|
55
|
+
newEvents.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
|
|
56
|
+
}
|
|
57
|
+
if (removed.length) {
|
|
58
|
+
console.log(chalk_1.default.yellow("\nRemoved events:"));
|
|
59
|
+
removed.forEach(e => console.log(chalk_1.default.yellow(`- ${e}`)));
|
|
60
|
+
}
|
|
50
61
|
process.exit(1);
|
|
51
62
|
}
|
|
52
63
|
console.log(chalk_1.default.green("\nAll good"));
|
package/dist/commands/send.js
CHANGED
|
@@ -47,6 +47,8 @@ async function send() {
|
|
|
47
47
|
}
|
|
48
48
|
console.log("");
|
|
49
49
|
console.log(chalk_1.default.blue(`Sending ${config.events.length} events...`));
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
50
52
|
try {
|
|
51
53
|
const res = await fetch(endpoint, {
|
|
52
54
|
method: "POST",
|
|
@@ -61,7 +63,8 @@ async function send() {
|
|
|
61
63
|
version: CLI_VERSION,
|
|
62
64
|
runtime: "node"
|
|
63
65
|
}
|
|
64
|
-
})
|
|
66
|
+
}),
|
|
67
|
+
signal: controller.signal
|
|
65
68
|
});
|
|
66
69
|
if (!res.ok) {
|
|
67
70
|
console.log(chalk_1.default.red(`Request failed (${res.status})`));
|
|
@@ -99,4 +102,7 @@ async function send() {
|
|
|
99
102
|
console.log(chalk_1.default.gray(err.message));
|
|
100
103
|
}
|
|
101
104
|
}
|
|
105
|
+
finally {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
}
|
|
102
108
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -29,9 +29,11 @@ async function sync() {
|
|
|
29
29
|
const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
|
|
30
30
|
engine.scanFile(virtualFile, content);
|
|
31
31
|
}
|
|
32
|
-
catch (
|
|
33
|
-
console.log(chalk_1.default.
|
|
34
|
-
|
|
32
|
+
catch (err) {
|
|
33
|
+
console.log(chalk_1.default.yellow(`Skipped: ${file}`));
|
|
34
|
+
if (err instanceof Error) {
|
|
35
|
+
console.log(chalk_1.default.gray(err.message));
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
const events = engine.getAllEvents().sort();
|
package/dist/commands/watch.js
CHANGED
|
@@ -10,28 +10,7 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
10
10
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
11
11
|
const config_1 = require("../utils/config");
|
|
12
12
|
const engine_1 = require("../engine/engine");
|
|
13
|
-
const
|
|
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
|
-
// 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
|
|
13
|
+
const processFile_1 = require("../utils/processFile");
|
|
35
14
|
async function watch() {
|
|
36
15
|
const config = await (0, config_1.loadConfig)();
|
|
37
16
|
if (!config)
|
|
@@ -40,17 +19,11 @@ async function watch() {
|
|
|
40
19
|
const files = await (0, fast_glob_1.default)(config.sync.include, {
|
|
41
20
|
ignore: config.sync.exclude,
|
|
42
21
|
});
|
|
43
|
-
if (!files.length) {
|
|
44
|
-
console.log(chalk_1.default.yellow("No files found"));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
22
|
const engine = new engine_1.EventraEngine(process.cwd());
|
|
48
|
-
// INITIAL SCAN
|
|
49
23
|
for (const file of files) {
|
|
50
24
|
try {
|
|
51
25
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
52
|
-
const content =
|
|
53
|
-
const virtualFile = toVirtualFile(file);
|
|
26
|
+
const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
|
|
54
27
|
engine.scanFile(virtualFile, content);
|
|
55
28
|
}
|
|
56
29
|
catch {
|
|
@@ -60,9 +33,9 @@ async function watch() {
|
|
|
60
33
|
config.events = engine.getAllEvents().sort();
|
|
61
34
|
await (0, config_1.saveConfig)(config);
|
|
62
35
|
console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
|
|
63
|
-
// STATE
|
|
64
36
|
let locked = false;
|
|
65
37
|
let queued = new Set();
|
|
38
|
+
let timer = null;
|
|
66
39
|
const run = async () => {
|
|
67
40
|
if (locked)
|
|
68
41
|
return;
|
|
@@ -72,8 +45,7 @@ async function watch() {
|
|
|
72
45
|
for (const file of batch) {
|
|
73
46
|
try {
|
|
74
47
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
75
|
-
const content =
|
|
76
|
-
const virtualFile = toVirtualFile(file);
|
|
48
|
+
const { content, virtualFile } = (0, processFile_1.processFile)(file, raw);
|
|
77
49
|
engine.updateFile(virtualFile, content);
|
|
78
50
|
}
|
|
79
51
|
catch {
|
|
@@ -94,39 +66,25 @@ async function watch() {
|
|
|
94
66
|
};
|
|
95
67
|
const schedule = (file) => {
|
|
96
68
|
queued.add(file);
|
|
97
|
-
|
|
69
|
+
if (timer)
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
timer = setTimeout(run, 100);
|
|
98
72
|
};
|
|
99
|
-
// WATCHER
|
|
100
73
|
const watcher = chokidar_1.default.watch(config.sync.include, {
|
|
101
74
|
ignored: config.sync.exclude,
|
|
102
75
|
ignoreInitial: true,
|
|
103
|
-
awaitWriteFinish: {
|
|
104
|
-
stabilityThreshold: 50,
|
|
105
|
-
pollInterval: 10,
|
|
106
|
-
},
|
|
107
76
|
});
|
|
108
77
|
watcher
|
|
109
|
-
.on("add",
|
|
110
|
-
|
|
111
|
-
schedule(file);
|
|
112
|
-
})
|
|
113
|
-
.on("change", (file) => {
|
|
114
|
-
console.log(chalk_1.default.gray(`change: ${file}`));
|
|
115
|
-
schedule(file);
|
|
116
|
-
})
|
|
78
|
+
.on("add", schedule)
|
|
79
|
+
.on("change", schedule)
|
|
117
80
|
.on("unlink", async (file) => {
|
|
118
81
|
console.log(chalk_1.default.gray(`remove: ${file}`));
|
|
119
|
-
const virtualFile =
|
|
120
|
-
engine.removeFile
|
|
82
|
+
const { virtualFile } = (0, processFile_1.processFile)(file, "");
|
|
83
|
+
engine.removeFile(virtualFile);
|
|
121
84
|
const next = engine.getAllEvents();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
|
|
125
|
-
config.events = next.sort();
|
|
126
|
-
await (0, config_1.saveConfig)(config);
|
|
127
|
-
}
|
|
85
|
+
config.events = next.sort();
|
|
86
|
+
await (0, config_1.saveConfig)(config);
|
|
128
87
|
});
|
|
129
|
-
// EXIT
|
|
130
88
|
process.on("SIGINT", async () => {
|
|
131
89
|
console.log(chalk_1.default.yellow("\nStopping watcher..."));
|
|
132
90
|
await watcher.close();
|
|
@@ -6,29 +6,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.resolveFunctionFromCall = resolveFunctionFromCall;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
function resolveFunctionFromCall(expr, checker) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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)) {
|
|
9
|
+
const type = checker.getTypeAtLocation(expr);
|
|
10
|
+
const signatures = type.getCallSignatures();
|
|
11
|
+
for (const sig of signatures) {
|
|
12
|
+
const decl = sig.getDeclaration();
|
|
13
|
+
if (decl &&
|
|
14
|
+
(typescript_1.default.isFunctionDeclaration(decl) ||
|
|
15
|
+
typescript_1.default.isMethodDeclaration(decl) ||
|
|
16
|
+
typescript_1.default.isFunctionExpression(decl) ||
|
|
17
|
+
typescript_1.default.isArrowFunction(decl))) {
|
|
23
18
|
return decl;
|
|
24
19
|
}
|
|
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
20
|
}
|
|
33
21
|
return null;
|
|
34
22
|
}
|
package/dist/engine/engine.js
CHANGED
|
@@ -30,7 +30,17 @@ class EventraEngine {
|
|
|
30
30
|
return events;
|
|
31
31
|
}
|
|
32
32
|
updateFile(file, content) {
|
|
33
|
-
|
|
33
|
+
const events = this.scanFile(file, content);
|
|
34
|
+
const dependents = this.graph.getDependents(file);
|
|
35
|
+
for (const dep of dependents) {
|
|
36
|
+
const source = this.ts.getSourceFile(dep);
|
|
37
|
+
const checker = this.ts.getChecker();
|
|
38
|
+
if (!source || !checker)
|
|
39
|
+
continue;
|
|
40
|
+
const ev = (0, scanner_1.scanSource)(source, checker);
|
|
41
|
+
this.fileEvents.set(dep, ev);
|
|
42
|
+
}
|
|
43
|
+
return events;
|
|
34
44
|
}
|
|
35
45
|
getAllEvents() {
|
|
36
46
|
const all = new Set();
|
|
@@ -42,6 +52,7 @@ class EventraEngine {
|
|
|
42
52
|
removeFile(file) {
|
|
43
53
|
this.fileEvents.delete(file);
|
|
44
54
|
this.fileHash.delete(file);
|
|
55
|
+
this.graph.remove(file);
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
exports.EventraEngine = EventraEngine;
|
|
@@ -0,0 +1,45 @@
|
|
|
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.findTrackParamIndex = findTrackParamIndex;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
function findTrackParamIndex(fn) {
|
|
9
|
+
const paramIndex = new Map();
|
|
10
|
+
fn.parameters.forEach((p, i) => {
|
|
11
|
+
if (typescript_1.default.isIdentifier(p.name)) {
|
|
12
|
+
paramIndex.set(p.name.text, i);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
let result = null;
|
|
16
|
+
function visit(node) {
|
|
17
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
18
|
+
const name = getCallName(node.expression);
|
|
19
|
+
if (/(track|event|capture|send)/i.test(name)) {
|
|
20
|
+
for (const arg of node.arguments) {
|
|
21
|
+
if (typescript_1.default.isIdentifier(arg)) {
|
|
22
|
+
const idx = paramIndex.get(arg.text);
|
|
23
|
+
if (idx !== undefined) {
|
|
24
|
+
result = idx;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
typescript_1.default.forEachChild(node, visit);
|
|
32
|
+
}
|
|
33
|
+
if (fn.body) {
|
|
34
|
+
visit(fn.body);
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function getCallName(expr) {
|
|
39
|
+
if (typescript_1.default.isIdentifier(expr))
|
|
40
|
+
return expr.text;
|
|
41
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
42
|
+
return expr.name.text;
|
|
43
|
+
}
|
|
44
|
+
return "";
|
|
45
|
+
}
|
package/dist/engine/graph.js
CHANGED
|
@@ -5,21 +5,89 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.DependencyGraph = void 0;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
10
|
class DependencyGraph {
|
|
9
11
|
reverse = new Map();
|
|
12
|
+
exports = new Map();
|
|
13
|
+
normalize(file) {
|
|
14
|
+
return path_1.default.resolve(file);
|
|
15
|
+
}
|
|
16
|
+
resolveImport(from, module) {
|
|
17
|
+
if (!module.startsWith("."))
|
|
18
|
+
return null;
|
|
19
|
+
const base = path_1.default.resolve(path_1.default.dirname(from), module);
|
|
20
|
+
const tryFiles = [
|
|
21
|
+
base,
|
|
22
|
+
base + ".ts",
|
|
23
|
+
base + ".tsx",
|
|
24
|
+
base + ".js",
|
|
25
|
+
base + ".jsx",
|
|
26
|
+
path_1.default.join(base, "index.ts"),
|
|
27
|
+
path_1.default.join(base, "index.tsx"),
|
|
28
|
+
path_1.default.join(base, "index.js"),
|
|
29
|
+
];
|
|
30
|
+
for (const f of tryFiles) {
|
|
31
|
+
if (fs_1.default.existsSync(f)) {
|
|
32
|
+
return this.normalize(f);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return this.normalize(base);
|
|
36
|
+
}
|
|
10
37
|
update(file, source) {
|
|
38
|
+
const normalizedFile = this.normalize(file);
|
|
39
|
+
this.exports.set(normalizedFile, new Set());
|
|
11
40
|
for (const stmt of source.statements) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
// IMPORT
|
|
42
|
+
if (typescript_1.default.isImportDeclaration(stmt)) {
|
|
43
|
+
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
44
|
+
const resolved = this.resolveImport(normalizedFile, module);
|
|
45
|
+
if (!resolved)
|
|
46
|
+
continue;
|
|
47
|
+
if (!this.reverse.has(resolved)) {
|
|
48
|
+
this.reverse.set(resolved, new Set());
|
|
49
|
+
}
|
|
50
|
+
this.reverse.get(resolved).add(normalizedFile);
|
|
51
|
+
}
|
|
52
|
+
// EXPORT
|
|
53
|
+
if (typescript_1.default.isExportDeclaration(stmt) && stmt.moduleSpecifier) {
|
|
54
|
+
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
55
|
+
const resolved = this.resolveImport(normalizedFile, module);
|
|
56
|
+
if (!resolved)
|
|
57
|
+
continue;
|
|
58
|
+
if (!this.exports.has(normalizedFile)) {
|
|
59
|
+
this.exports.set(normalizedFile, new Set());
|
|
60
|
+
}
|
|
61
|
+
this.exports.get(normalizedFile).add(resolved);
|
|
62
|
+
if (!this.reverse.has(resolved)) {
|
|
63
|
+
this.reverse.set(resolved, new Set());
|
|
64
|
+
}
|
|
65
|
+
this.reverse.get(resolved).add(normalizedFile);
|
|
17
66
|
}
|
|
18
|
-
this.reverse.get(module).add(file);
|
|
19
67
|
}
|
|
20
68
|
}
|
|
69
|
+
getAllRelated(file, visited = new Set()) {
|
|
70
|
+
const normalized = this.normalize(file);
|
|
71
|
+
if (visited.has(normalized))
|
|
72
|
+
return visited;
|
|
73
|
+
visited.add(normalized);
|
|
74
|
+
const deps = this.exports.get(normalized);
|
|
75
|
+
if (!deps)
|
|
76
|
+
return visited;
|
|
77
|
+
for (const dep of deps) {
|
|
78
|
+
this.getAllRelated(dep, visited);
|
|
79
|
+
}
|
|
80
|
+
return visited;
|
|
81
|
+
}
|
|
21
82
|
getDependents(file) {
|
|
22
|
-
return [...(this.reverse.get(file) ?? [])];
|
|
83
|
+
return [...(this.reverse.get(this.normalize(file)) ?? [])];
|
|
84
|
+
}
|
|
85
|
+
remove(file) {
|
|
86
|
+
const normalized = this.normalize(file);
|
|
87
|
+
this.exports.delete(normalized);
|
|
88
|
+
for (const set of this.reverse.values()) {
|
|
89
|
+
set.delete(normalized);
|
|
90
|
+
}
|
|
23
91
|
}
|
|
24
92
|
}
|
|
25
93
|
exports.DependencyGraph = DependencyGraph;
|
|
@@ -9,11 +9,29 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
class TSService {
|
|
10
10
|
root;
|
|
11
11
|
files = new Map();
|
|
12
|
+
fileNames = new Set();
|
|
12
13
|
service;
|
|
13
14
|
constructor(root) {
|
|
14
15
|
this.root = root;
|
|
16
|
+
const configPath = typescript_1.default.findConfigFile(root, typescript_1.default.sys.fileExists, "tsconfig.json");
|
|
17
|
+
let compilerOptions = {
|
|
18
|
+
allowJs: true,
|
|
19
|
+
jsx: typescript_1.default.JsxEmit.Preserve,
|
|
20
|
+
moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
|
|
21
|
+
target: typescript_1.default.ScriptTarget.ESNext,
|
|
22
|
+
esModuleInterop: true,
|
|
23
|
+
allowSyntheticDefaultImports: true,
|
|
24
|
+
};
|
|
25
|
+
if (configPath) {
|
|
26
|
+
const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
27
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, root);
|
|
28
|
+
compilerOptions = {
|
|
29
|
+
...compilerOptions,
|
|
30
|
+
...parsed.options,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
15
33
|
const host = {
|
|
16
|
-
getScriptFileNames: () => [...this.
|
|
34
|
+
getScriptFileNames: () => [...this.fileNames],
|
|
17
35
|
getScriptVersion: (file) => this.files.get(file)?.version.toString() ?? "0",
|
|
18
36
|
getScriptSnapshot: (file) => {
|
|
19
37
|
const f = this.files.get(file);
|
|
@@ -26,24 +44,11 @@ class TSService {
|
|
|
26
44
|
return undefined;
|
|
27
45
|
},
|
|
28
46
|
getCurrentDirectory: () => this.root,
|
|
29
|
-
getCompilationSettings: () =>
|
|
30
|
-
allowJs: true,
|
|
31
|
-
jsx: typescript_1.default.JsxEmit.Preserve,
|
|
32
|
-
moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
|
|
33
|
-
target: typescript_1.default.ScriptTarget.ESNext,
|
|
34
|
-
esModuleInterop: true,
|
|
35
|
-
allowSyntheticDefaultImports: true,
|
|
36
|
-
}),
|
|
47
|
+
getCompilationSettings: () => compilerOptions,
|
|
37
48
|
getDefaultLibFileName: (opts) => typescript_1.default.getDefaultLibFilePath(opts),
|
|
38
|
-
readFile:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
fileExists: fs_1.default.existsSync,
|
|
49
|
+
readFile: typescript_1.default.sys.readFile,
|
|
50
|
+
fileExists: typescript_1.default.sys.fileExists,
|
|
51
|
+
readDirectory: typescript_1.default.sys.readDirectory,
|
|
47
52
|
};
|
|
48
53
|
this.service = typescript_1.default.createLanguageService(host);
|
|
49
54
|
}
|
|
@@ -53,6 +58,7 @@ class TSService {
|
|
|
53
58
|
content,
|
|
54
59
|
version: prev ? prev.version + 1 : 1,
|
|
55
60
|
});
|
|
61
|
+
this.fileNames.add(file);
|
|
56
62
|
}
|
|
57
63
|
getProgram() {
|
|
58
64
|
return this.service.getProgram();
|