@eventra_dev/eventra-cli 0.2.3 → 0.3.1

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 (89) hide show
  1. package/README.md +154 -32
  2. package/dist/analysis/cache/evaluationCache.js +21 -0
  3. package/dist/analysis/cache/resolvedCallCache.js +21 -0
  4. package/dist/analysis/cache/resolvedExportCache.js +21 -0
  5. package/dist/analysis/cache/semanticDependencyGraph.js +25 -0
  6. package/dist/analysis/cache/symbolInvalidation.js +20 -0
  7. package/dist/analysis/extractor/extractor.js +47 -0
  8. package/dist/analysis/extractor/propagationExtractor.js +52 -0
  9. package/dist/analysis/pipeline/semanticPass.js +2 -0
  10. package/dist/analysis/resolver/callResolver.js +125 -0
  11. package/dist/analysis/resolver/exportResolver.js +75 -0
  12. package/dist/analysis/resolver/resolver.js +133 -0
  13. package/dist/analysis/scanner/analyzer/propagationAnalyzer.js +193 -0
  14. package/dist/analysis/scanner/analyzer/returnPropagationAnalyzer.js +105 -0
  15. package/dist/analysis/scanner/scanner.js +117 -0
  16. package/dist/analysis/scanner/sinkDetector.js +91 -0
  17. package/dist/analysis/shared/evaluationContext.js +9 -0
  18. package/dist/analysis/shared/propagation.js +3 -0
  19. package/dist/analysis/shared/returnPropagation.js +2 -0
  20. package/dist/analysis/shared/types.js +2 -0
  21. package/dist/analysis/shared/utils.js +62 -0
  22. package/dist/analysis/symbols/symbolUtils.js +68 -0
  23. package/dist/analysis/symbols/wrapperRegistry.js +55 -0
  24. package/dist/cli/check.js +73 -0
  25. package/dist/{commands → cli}/init.js +1 -1
  26. package/dist/{commands → cli}/send.js +1 -1
  27. package/dist/cli/sync.js +56 -0
  28. package/dist/{commands → cli}/watch.js +41 -25
  29. package/dist/compiler/compilerContext.js +204 -0
  30. package/dist/compiler/documentRegistry.js +78 -0
  31. package/dist/compiler/importGraph.js +83 -0
  32. package/dist/compiler/scheduler.js +29 -0
  33. package/dist/core/EventraEngine.js +234 -0
  34. package/dist/core/projectScanner.js +73 -0
  35. package/dist/{utils → filesystem}/getVirtualFile.js +1 -3
  36. package/dist/filesystem/processFile.js +38 -0
  37. package/dist/index.js +5 -5
  38. package/dist/shared/buildSyntheticTsx.js +20 -0
  39. package/dist/shared/templateExpressionExtractor.js +22 -0
  40. package/package.json +32 -9
  41. package/.github/workflows/release.yml +0 -43
  42. package/ARCHITECTURE.md +0 -88
  43. package/assets/eventra-icon-animated.svg +0 -19
  44. package/dist/commands/check.js +0 -78
  45. package/dist/commands/sync.js +0 -79
  46. package/dist/engine/boundary.js +0 -14
  47. package/dist/engine/callResolver.js +0 -58
  48. package/dist/engine/engine.js +0 -154
  49. package/dist/engine/exportResolver.js +0 -46
  50. package/dist/engine/functionAnalyzer.js +0 -71
  51. package/dist/engine/graph.js +0 -168
  52. package/dist/engine/languageService.js +0 -162
  53. package/dist/engine/resolver.js +0 -149
  54. package/dist/engine/scanner.js +0 -230
  55. package/dist/utils/processFile.js +0 -28
  56. package/dist/utils/universal.js +0 -190
  57. package/src/commands/check.ts +0 -91
  58. package/src/commands/init.ts +0 -174
  59. package/src/commands/send.ts +0 -213
  60. package/src/commands/sync.ts +0 -106
  61. package/src/commands/watch.ts +0 -144
  62. package/src/engine/boundary.ts +0 -12
  63. package/src/engine/callResolver.ts +0 -65
  64. package/src/engine/engine.ts +0 -227
  65. package/src/engine/exportResolver.ts +0 -56
  66. package/src/engine/functionAnalyzer.ts +0 -81
  67. package/src/engine/graph.ts +0 -193
  68. package/src/engine/hash.ts +0 -5
  69. package/src/engine/languageService.ts +0 -209
  70. package/src/engine/resolver.ts +0 -177
  71. package/src/engine/scanner.ts +0 -298
  72. package/src/index.ts +0 -49
  73. package/src/types.ts +0 -35
  74. package/src/utils/config.ts +0 -53
  75. package/src/utils/getVirtualFile.ts +0 -5
  76. package/src/utils/processFile.ts +0 -29
  77. package/src/utils/universal.ts +0 -249
  78. package/tests/fixtures/backend/express/app.ts +0 -233
  79. package/tests/fixtures/backend/nest/service.ts +0 -70
  80. package/tests/fixtures/backend/node/index.ts +0 -63
  81. package/tests/fixtures/frontend/next/page.tsx +0 -101
  82. package/tests/fixtures/frontend/react/App.tsx +0 -104
  83. package/tests/fixtures/frontend/vue/App.vue +0 -83
  84. package/tests/fixtures/wrappers/component/test.tsx +0 -62
  85. package/tests/fixtures/wrappers/function/test.ts +0 -60
  86. package/tests/run.ts +0 -106
  87. package/tsconfig.json +0 -14
  88. /package/dist/{utils → config}/config.js +0 -0
  89. /package/dist/{engine → shared}/hash.js +0 -0
@@ -1,227 +0,0 @@
1
- import path from "path";
2
- import fs from "fs";
3
-
4
- import { TSService } from "./languageService";
5
- import { scanSource } from "./scanner";
6
- import { DependencyGraph } from "./graph";
7
- import { hash } from "./hash";
8
- import { EventraConfig, ScanResult } from "../types";
9
- import { isExternalFile } from "./boundary";
10
-
11
- export class EventraEngine {
12
- private ts: TSService;
13
- private graph: DependencyGraph;
14
- private wrapperCache = new Map<string, string | null>();
15
- private fileResults = new Map<string, ScanResult>();
16
- private fileHash = new Map<string, string>();
17
-
18
- constructor(root: string) {
19
- this.ts = new TSService(root);
20
-
21
- const { baseUrl, paths } = this.ts.getModuleResolutionConfig();
22
- this.graph = new DependencyGraph(baseUrl, paths);
23
- }
24
-
25
- private normalize(file: string) {
26
- return path.resolve(file).replace(/\\/g, "/");
27
- }
28
-
29
- private empty(): ScanResult {
30
- return {
31
- events: new Set(),
32
- detectedFunctionWrappers: new Set(),
33
- detectedComponentWrappers: new Map(),
34
- };
35
- }
36
-
37
- // PRELOAD
38
- preloadFile(file: string, content: string) {
39
- file = this.normalize(file);
40
-
41
- this.ts.updateFile(file, content);
42
-
43
- const source = this.ts.getSourceFile(file);
44
- if (source) {
45
- this.graph.update(file, source);
46
- }
47
- }
48
-
49
- // SCAN FILE (cached)
50
- scanFile(
51
- file: string,
52
- content: string,
53
- config: EventraConfig
54
- ): ScanResult {
55
- file = this.normalize(file);
56
-
57
- const h = hash(content);
58
-
59
- if (this.fileHash.get(file) === h) {
60
- return this.fileResults.get(file) ?? this.empty();
61
- }
62
-
63
- this.fileHash.set(file, h);
64
-
65
- this.ts.updateFile(file, content);
66
-
67
- const source = this.ts.getSourceFile(file);
68
- const checker = this.ts.getChecker();
69
-
70
- if (!source || !checker) {
71
- return this.empty();
72
- }
73
-
74
- this.graph.update(file, source);
75
-
76
- const res = scanSource(
77
- source,
78
- checker,
79
- config,
80
- new Set(),
81
- undefined,
82
- this.wrapperCache
83
- );
84
-
85
- this.fileResults.set(file, res);
86
-
87
- return res;
88
- }
89
-
90
- // UPDATE FILE (incremental)
91
- updateFile(
92
- file: string,
93
- content: string,
94
- config: EventraConfig
95
- ): ScanResult {
96
- file = this.normalize(file);
97
-
98
- // EARLY EXIT
99
- if (this.fileHash.get(file) === hash(content)) {
100
- return this.fileResults.get(file) ?? this.empty();
101
- }
102
-
103
- // invalidate wrapper cache
104
- this.wrapperCache.clear();
105
-
106
- const res = this.scanFile(file, content, config);
107
-
108
- const dependents = this.graph.getAllDependentsDeep(file);
109
-
110
- for (const dep of dependents) {
111
- const abs = this.normalize(dep);
112
-
113
- if (abs === file) continue; // self-skip
114
- if (isExternalFile(abs)) continue;
115
-
116
- let depContent = this.ts.getFileContent(abs);
117
-
118
- if (!depContent) {
119
- try {
120
- depContent = fs.readFileSync(abs, "utf-8");
121
- } catch {
122
- continue;
123
- }
124
- }
125
-
126
- const h = hash(depContent);
127
-
128
- if (this.fileHash.get(abs) === h) {
129
- continue;
130
- }
131
-
132
- this.fileHash.set(abs, h);
133
-
134
- this.ts.updateFile(abs, depContent);
135
-
136
- const source = this.ts.getSourceFile(abs);
137
- const checker = this.ts.getChecker();
138
-
139
- if (!source || !checker) continue;
140
-
141
- this.graph.update(abs, source);
142
-
143
- const r = scanSource(
144
- source,
145
- checker,
146
- config,
147
- new Set(),
148
- undefined,
149
- this.wrapperCache
150
- );
151
-
152
- this.fileResults.set(abs, r);
153
- }
154
-
155
- return res;
156
- }
157
-
158
- // GET ALL EVENTS
159
- getAllEvents() {
160
- const all = new Set<string>();
161
-
162
- for (const r of this.fileResults.values()) {
163
- r.events.forEach(e => all.add(e));
164
- }
165
-
166
- return [...all];
167
- }
168
-
169
- // REMOVE FILE
170
- removeFile(file: string, config?: EventraConfig) {
171
- file = this.normalize(file);
172
-
173
- this.wrapperCache.clear();
174
-
175
- const dependents = this.graph.getAllDependentsDeep(file);
176
-
177
- this.graph.remove(file);
178
-
179
- this.ts.removeFile(file);
180
- this.fileResults.delete(file);
181
- this.fileHash.delete(file);
182
-
183
- for (const dep of dependents) {
184
- const abs = this.normalize(dep);
185
-
186
- if (abs === file) continue;
187
- if (isExternalFile(abs)) continue;
188
-
189
- let content = this.ts.getFileContent(abs);
190
-
191
- if (!content) {
192
- try {
193
- content = fs.readFileSync(abs, "utf-8");
194
- } catch {
195
- continue;
196
- }
197
- }
198
-
199
- this.fileHash.delete(abs);
200
-
201
- this.ts.updateFile(abs, content);
202
-
203
- const source = this.ts.getSourceFile(abs);
204
- const checker = this.ts.getChecker();
205
-
206
- if (!source || !checker) continue;
207
-
208
- this.graph.update(abs, source);
209
-
210
- const r = scanSource(
211
- source,
212
- checker,
213
- config ?? {
214
- events: [],
215
- wrappers: [],
216
- functionWrappers: [],
217
- sync: { include: [], exclude: [] },
218
- },
219
- new Set(),
220
- undefined,
221
- this.wrapperCache
222
- );
223
-
224
- this.fileResults.set(abs, r);
225
- }
226
- }
227
- }
@@ -1,56 +0,0 @@
1
- import ts from "typescript";
2
-
3
- function isRealDeclaration(node: ts.Node) {
4
- return (
5
- ts.isFunctionDeclaration(node) ||
6
- ts.isVariableDeclaration(node) ||
7
- ts.isClassDeclaration(node)
8
- );
9
- }
10
-
11
- export function resolveExportedSymbol(
12
- symbol: ts.Symbol,
13
- checker: ts.TypeChecker,
14
- visited = new Set<ts.Symbol>()
15
- ): ts.Symbol | null {
16
-
17
- if (visited.has(symbol)) return null;
18
- visited.add(symbol);
19
-
20
- // unwrap alias
21
- if (symbol.flags & ts.SymbolFlags.Alias) {
22
- const aliased = checker.getAliasedSymbol(symbol);
23
- return resolveExportedSymbol(aliased, checker, visited);
24
- }
25
-
26
- // export symbol
27
- const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
28
- if (exportSymbol && exportSymbol !== symbol) {
29
- return resolveExportedSymbol(exportSymbol, checker, visited);
30
- }
31
-
32
- const decls = symbol.getDeclarations() ?? [];
33
-
34
- if (decls.some(isRealDeclaration)) {
35
- return symbol;
36
- }
37
-
38
- // exports (re-export)
39
- for (const decl of decls) {
40
- const source = decl.getSourceFile();
41
- const moduleSymbol = (source as any).symbol;
42
-
43
- if (!moduleSymbol) continue;
44
-
45
- const exports = checker.getExportsOfModule(moduleSymbol);
46
-
47
- for (const exp of exports) {
48
- const resolved = resolveExportedSymbol(exp, checker, visited);
49
- if (resolved?.getDeclarations()?.length) {
50
- return resolved;
51
- }
52
- }
53
- }
54
-
55
- return symbol;
56
- }
@@ -1,81 +0,0 @@
1
- import ts from "typescript";
2
-
3
- export function findTrackParamIndex(
4
- fn: ts.FunctionLikeDeclaration
5
- ): number | null {
6
- const paramIndex = new Map<string, number>();
7
-
8
- fn.parameters.forEach((p, i) => {
9
- if (ts.isIdentifier(p.name)) {
10
- paramIndex.set(p.name.text, i);
11
- }
12
- });
13
-
14
- let result: number | null = null;
15
-
16
- function check(arg: ts.Expression): boolean {
17
- // identifier
18
- if (ts.isIdentifier(arg)) {
19
- const idx = paramIndex.get(arg.text);
20
- if (idx !== undefined) {
21
- result = idx;
22
- return true;
23
- }
24
- }
25
-
26
- // template
27
- if (ts.isTemplateExpression(arg)) {
28
- for (const span of arg.templateSpans) {
29
- if (ts.isIdentifier(span.expression)) {
30
- const idx = paramIndex.get(span.expression.text);
31
- if (idx !== undefined) {
32
- result = idx;
33
- return true;
34
- }
35
- }
36
- }
37
- }
38
-
39
- // object { event: name }
40
- if (ts.isObjectLiteralExpression(arg)) {
41
- for (const prop of arg.properties) {
42
- if (!ts.isPropertyAssignment(prop)) continue;
43
-
44
- const key = prop.name.getText();
45
-
46
- if (["event", "name", "type"].includes(key)) {
47
- return check(prop.initializer);
48
- }
49
- }
50
- }
51
-
52
- return false;
53
- }
54
-
55
- function visit(node: ts.Node) {
56
- if (result !== null) return;
57
-
58
- if (ts.isCallExpression(node)) {
59
- const expr = node.expression;
60
-
61
- const isTrack =
62
- (ts.isIdentifier(expr) && expr.text === "track") ||
63
- (ts.isPropertyAccessExpression(expr) &&
64
- expr.name.text === "track");
65
-
66
- if (!isTrack) return;
67
-
68
- for (const arg of node.arguments) {
69
- if (check(arg)) return;
70
- }
71
- }
72
-
73
- ts.forEachChild(node, visit);
74
- }
75
-
76
- if (fn.body) {
77
- visit(fn.body);
78
- }
79
-
80
- return result;
81
- }
@@ -1,193 +0,0 @@
1
- import ts from "typescript";
2
- import path from "path";
3
- import fs from "fs";
4
-
5
- export class DependencyGraph {
6
- private reverse = new Map<string, Set<string>>();
7
- private exports = new Map<string, Set<string>>();
8
- private fileCache = new Map<string, string | null>();
9
-
10
- constructor(
11
- private baseUrl: string,
12
- private paths: Record<string, string[]>
13
- ) {}
14
-
15
- private normalize(file: string) {
16
- return path.resolve(file).replace(/\\/g, "/");
17
- }
18
-
19
- // MODULE RESOLVE
20
- private resolveModule(module: string, from: string): string | null {
21
- // relative import
22
- if (module.startsWith(".")) {
23
- const abs = path.resolve(path.dirname(from), module);
24
- return this.resolveFile(abs);
25
- }
26
-
27
- // tsconfig paths
28
- for (const key in this.paths) {
29
- const pattern = key.replace(/\*/g, "(.*)");
30
- const regex = new RegExp("^" + pattern + "$");
31
- const match = module.match(regex);
32
-
33
- if (!match) continue;
34
-
35
- const wildcard = match.slice(1);
36
-
37
- for (const target of this.paths[key]) {
38
- let replaced = target;
39
-
40
- wildcard.forEach(w => {
41
- replaced = replaced.replace("*", w);
42
- });
43
-
44
- const abs = path.resolve(this.baseUrl, replaced);
45
- const resolved = this.resolveFile(abs);
46
- if (resolved) return resolved;
47
- }
48
- }
49
-
50
- // node resolution (safe)
51
- try {
52
- const resolved = require.resolve(module, {
53
- paths: [path.dirname(from)],
54
- });
55
-
56
- return this.normalize(resolved);
57
- } catch {}
58
-
59
- // fallback
60
- const abs = path.resolve(this.baseUrl, module);
61
- return this.resolveFile(abs);
62
- }
63
-
64
- // FILE RESOLVE
65
- private resolveFile(base: string): string | null {
66
- if (this.fileCache.has(base)) {
67
- return this.fileCache.get(base)!;
68
- }
69
-
70
- const tryFiles = [
71
- base,
72
- base + ".ts",
73
- base + ".tsx",
74
- base + ".js",
75
- base + ".jsx",
76
- base + ".mjs",
77
- base + ".cjs",
78
- base + ".d.ts",
79
- base + ".vue",
80
- base + ".svelte",
81
- base + ".astro",
82
- path.join(base, "index.ts"),
83
- path.join(base, "index.tsx"),
84
- path.join(base, "index.js"),
85
- ];
86
-
87
- for (const f of tryFiles) {
88
- if (fs.existsSync(f)) {
89
- const normalized = this.normalize(f);
90
- this.fileCache.set(base, normalized);
91
- return normalized;
92
- }
93
- }
94
-
95
- this.fileCache.set(base, null);
96
- return null;
97
- }
98
-
99
- // UPDATE GRAPH
100
- update(file: string, source: ts.SourceFile) {
101
- const normalizedFile = this.normalize(file);
102
-
103
- for (const deps of this.reverse.values()) {
104
- deps.delete(normalizedFile);
105
- }
106
-
107
- this.exports.set(normalizedFile, new Set());
108
-
109
- for (const stmt of source.statements) {
110
-
111
- // IMPORT
112
- if (ts.isImportDeclaration(stmt)) {
113
- // ignore import type
114
- if ((stmt.importClause as any)?.importKind === "type") continue;
115
-
116
- const module = stmt.moduleSpecifier
117
- .getText()
118
- .replace(/['"]/g, "");
119
-
120
- const resolved = this.resolveModule(module, normalizedFile);
121
- if (!resolved) continue;
122
-
123
- if (!this.reverse.has(resolved)) {
124
- this.reverse.set(resolved, new Set());
125
- }
126
-
127
- this.reverse.get(resolved)!.add(normalizedFile);
128
- }
129
-
130
- // EXPORT (re-export)
131
- if (ts.isExportDeclaration(stmt)) {
132
- if (!stmt.moduleSpecifier) continue;
133
-
134
- const module = stmt.moduleSpecifier
135
- .getText()
136
- .replace(/['"]/g, "");
137
-
138
- const resolved = this.resolveModule(module, normalizedFile);
139
- if (!resolved) continue;
140
-
141
- this.exports.get(normalizedFile)?.add(resolved);
142
-
143
- if (!this.reverse.has(resolved)) {
144
- this.reverse.set(resolved, new Set());
145
- }
146
-
147
- this.reverse.get(resolved)!.add(normalizedFile);
148
- }
149
- }
150
- }
151
-
152
- // DEPENDENTS
153
- getAllDependentsDeep(file: string): string[] {
154
- const start = this.normalize(file);
155
- const visited = new Set<string>();
156
- const result = new Set<string>();
157
-
158
- const walk = (f: string) => {
159
- if (visited.has(f)) return;
160
- visited.add(f);
161
-
162
- const deps = this.reverse.get(f);
163
- deps?.forEach(d => {
164
- result.add(d);
165
- walk(d);
166
- });
167
-
168
- const ex = this.exports.get(f);
169
- ex?.forEach(e => walk(e));
170
- };
171
-
172
- walk(start);
173
-
174
- return [...result];
175
- }
176
-
177
- // REMOVE
178
- remove(file: string) {
179
- const normalized = this.normalize(file);
180
-
181
- this.reverse.delete(normalized);
182
- this.exports.delete(normalized);
183
- this.fileCache.delete(normalized);
184
-
185
- for (const set of this.reverse.values()) {
186
- set.delete(normalized);
187
- }
188
-
189
- for (const set of this.exports.values()) {
190
- set.delete(normalized);
191
- }
192
- }
193
- }
@@ -1,5 +0,0 @@
1
- import crypto from "crypto";
2
-
3
- export function hash(content: string) {
4
- return crypto.createHash("md5").update(content).digest("hex");
5
- }