@eventra_dev/eventra-cli 0.2.4 → 0.3.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 (89) hide show
  1. package/README.md +143 -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 -152
  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 -197
  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,174 +0,0 @@
1
- import chalk from "chalk";
2
- import inquirer from "inquirer";
3
-
4
- import { saveConfig } from "../utils/config";
5
- import {ComponentWrapper, FunctionWrapper} from "../types";
6
-
7
- export async function init() {
8
- console.log(
9
- chalk.blue("Initializing Eventra...")
10
- );
11
-
12
- // API KEY ENDPOINT
13
- const { apiKey, endpoint } =
14
- await inquirer.prompt([
15
- {
16
- type: "input",
17
- name: "apiKey",
18
- message:
19
- "API Key (optional):"
20
- },
21
- {
22
- type: "input",
23
- name: "endpoint",
24
- message: "Custom endpoint (optional):",
25
- default: "https://api.eventra.dev/api/v1/cli/events"
26
- }
27
- ]);
28
-
29
- console.log(
30
- chalk.gray(
31
- "\nEventra automatically detects:"
32
- )
33
- );
34
-
35
- console.log(
36
- chalk.gray("• track('event')")
37
- );
38
-
39
- console.log(
40
- chalk.gray(
41
- "• tracker.track('event')"
42
- )
43
- );
44
-
45
- const wrappers: ComponentWrapper[] = [];
46
- const functionWrappers: FunctionWrapper[] = [];
47
-
48
-
49
- // COMPONENT WRAPPERS
50
- console.log(
51
- chalk.blue(
52
- "\nComponent wrappers"
53
- )
54
- );
55
-
56
- let addComponent = true;
57
-
58
- while (addComponent) {
59
- const { useWrapper } =
60
- await inquirer.prompt([
61
- {
62
- type: "confirm",
63
- name: "useWrapper",
64
- message:
65
- "Add component wrapper?",
66
- default: false
67
- }
68
- ]);
69
-
70
- if (!useWrapper) break;
71
-
72
- const { name, prop } =
73
- await inquirer.prompt([
74
- {
75
- type: "input",
76
- name: "name",
77
- message: "Component name:",
78
- validate: (v) =>
79
- v ? true : "Required"
80
- },
81
- {
82
- type: "input",
83
- name: "prop",
84
- message:
85
- "Event prop:",
86
- default: "event"
87
- }
88
- ]);
89
-
90
- wrappers.push({
91
- name,
92
- prop
93
- });
94
- }
95
-
96
- // FUNCTION WRAPPERS
97
- console.log(
98
- chalk.blue(
99
- "\nFunction wrappers"
100
- )
101
- );
102
-
103
- let addFunction = true;
104
-
105
- while (addFunction) {
106
- const { useWrapper } =
107
- await inquirer.prompt([
108
- {
109
- type: "confirm",
110
- name: "useWrapper",
111
- message:
112
- "Add function wrapper?",
113
- default: false
114
- }
115
- ]);
116
-
117
- if (!useWrapper) break;
118
-
119
- const { name, event } =
120
- await inquirer.prompt([
121
- {
122
- type: "input",
123
- name: "name",
124
- message:
125
- "Function name:"
126
- },
127
- {
128
- type: "input",
129
- name: "event",
130
- message:
131
- "Event field (leave empty if string argument):",
132
- default: ""
133
- }
134
- ]);
135
-
136
- functionWrappers.push({
137
- name,
138
- event: event || undefined
139
- });
140
- }
141
-
142
- const config = {
143
- apiKey,
144
- endpoint,
145
- events: [],
146
- wrappers,
147
- functionWrappers,
148
- sync: {
149
- include: [
150
- "**/*.{ts,tsx,js,jsx,vue,svelte,astro}"
151
- ],
152
- exclude: [
153
- "node_modules",
154
- "dist",
155
- ".next",
156
- ".git"
157
- ]
158
- }
159
- };
160
-
161
- await saveConfig(config);
162
-
163
- console.log(
164
- chalk.green(
165
- "\neventra.json created"
166
- )
167
- );
168
-
169
- console.log(
170
- chalk.gray(
171
- "\nRun `eventra sync`"
172
- )
173
- );
174
- }
@@ -1,213 +0,0 @@
1
- import chalk from "chalk";
2
- import inquirer from "inquirer";
3
- import {
4
- loadConfig,
5
- saveConfig
6
- } from "../utils/config";
7
-
8
- import pkg from "../../package.json";
9
-
10
- const CLI_VERSION = pkg.version;
11
-
12
- export async function send() {
13
- const config = await loadConfig();
14
-
15
- const endpoint =
16
- config?.endpoint ||
17
- process.env.EVENTRA_ENDPOINT;
18
-
19
- if (!config) {
20
- console.log(
21
- chalk.red(
22
- "eventra.json not found. Run 'eventra init'"
23
- )
24
- );
25
- return;
26
- }
27
-
28
- let apiKey = config.apiKey;
29
-
30
- // ask api key
31
- if (!apiKey) {
32
- const answers =
33
- await inquirer.prompt([
34
- {
35
- type: "input",
36
- name: "apiKey",
37
- message:
38
- "Enter your API key:"
39
- }
40
- ]);
41
-
42
- apiKey = answers.apiKey;
43
-
44
- config.apiKey = apiKey;
45
-
46
- await saveConfig(config);
47
-
48
- console.log(
49
- chalk.green("API key saved")
50
- );
51
- }
52
-
53
- // no events
54
- if (!config.events?.length) {
55
- console.log(
56
- chalk.yellow(
57
- "No events found. Run 'eventra sync'"
58
- )
59
- );
60
- return;
61
- }
62
-
63
- if (!apiKey) {
64
- console.log(
65
- chalk.red("API key required")
66
- );
67
- return;
68
- }
69
-
70
- if (!endpoint) {
71
- console.log(
72
- chalk.red("Endpoint not configured")
73
- );
74
- return;
75
- }
76
-
77
- console.log("");
78
- console.log(
79
- chalk.blue(
80
- `Sending ${config.events.length} events...`
81
- )
82
- );
83
-
84
- const controller = new AbortController();
85
- const timeout = setTimeout(() => controller.abort(), 10000);
86
-
87
- try {
88
- const res = await fetch(
89
- endpoint,
90
- {
91
- method: "POST",
92
- headers: {
93
- "Content-Type":
94
- "application/json",
95
- "x-api-key": apiKey
96
- },
97
- body: JSON.stringify({
98
- events: config.events,
99
- cli: {
100
- name:
101
- "@eventra_dev/eventra-cli",
102
- version: CLI_VERSION,
103
- runtime: "node"
104
- }
105
- }),
106
- signal: controller.signal
107
- },
108
- );
109
-
110
- if (!res.ok) {
111
- console.log(
112
- chalk.red(
113
- `Request failed (${res.status})`
114
- )
115
- );
116
-
117
- try {
118
- const text =
119
- await res.text();
120
-
121
- console.log(
122
- chalk.gray(text)
123
- );
124
- } catch {}
125
-
126
- return;
127
- }
128
-
129
- const data =
130
- await res.json();
131
-
132
- console.log(
133
- chalk.green(
134
- "Events registered successfully"
135
- )
136
- );
137
-
138
- // created
139
- if (data.created?.length) {
140
- console.log(
141
- chalk.green("\nNew events:")
142
- );
143
-
144
- data.created.forEach(
145
- (e: string) =>
146
- console.log(
147
- chalk.green(
148
- `+ ${e}`
149
- )
150
- )
151
- );
152
- }
153
-
154
- // existing
155
- if (data.existing?.length) {
156
- console.log(
157
- chalk.gray(
158
- "\nExisting events:"
159
- )
160
- );
161
-
162
- data.existing.forEach(
163
- (e: string) =>
164
- console.log(
165
- chalk.gray(
166
- `• ${e}`
167
- )
168
- )
169
- );
170
- }
171
-
172
- // processing notice
173
- if (data.created?.length) {
174
- console.log("");
175
- console.log(
176
- chalk.yellow(
177
- "Events queued for processing (~2 min)"
178
- )
179
- );
180
-
181
- console.log(
182
- chalk.gray(
183
- "They will appear in dashboard shortly"
184
- )
185
- );
186
- }
187
-
188
- console.log("");
189
-
190
- console.log(
191
- chalk.gray(
192
- `Sent ${config.events.length} events`
193
- )
194
- );
195
-
196
- } catch (err) {
197
- console.log(
198
- chalk.red(
199
- "Network error"
200
- )
201
- );
202
-
203
- if (err instanceof Error) {
204
- console.log(
205
- chalk.gray(
206
- err.message
207
- )
208
- );
209
- }
210
- } finally {
211
- clearTimeout(timeout);
212
- }
213
- }
@@ -1,106 +0,0 @@
1
- import fg from "fast-glob";
2
- import fs from "fs/promises";
3
- import chalk from "chalk";
4
- import path from "path";
5
-
6
- import { loadConfig, saveConfig } from "../utils/config";
7
- import { EventraEngine } from "../engine/engine";
8
- import { processFile } from "../utils/processFile";
9
- import { getVirtualFile } from "../utils/getVirtualFile";
10
-
11
- export async function sync() {
12
- const config = await loadConfig();
13
- if (!config) return;
14
-
15
- console.log(chalk.blue("Scanning..."));
16
-
17
- const files = await fg(config.sync.include, {
18
- ignore: config.sync.exclude,
19
- absolute: true,
20
- });
21
-
22
- const engine = new EventraEngine(process.cwd());
23
-
24
- const cache = new Map<string, { content: string; deps: string[] }>();
25
- const toScan = new Set<string>();
26
-
27
- // COLLECT
28
- for (const file of files) {
29
- try {
30
- const raw = await fs.readFile(file, "utf-8");
31
- if (raw.length > 2_000_000) continue;
32
-
33
- const parsed = processFile(file, raw);
34
-
35
- cache.set(file, parsed);
36
- toScan.add(file);
37
-
38
- parsed.deps.forEach(dep => toScan.add(path.resolve(dep)));
39
-
40
- } catch {}
41
- }
42
-
43
- // PRELOAD
44
- for (const file of toScan) {
45
- try {
46
- let parsed = cache.get(file);
47
-
48
- if (!parsed) {
49
- const raw = await fs.readFile(file, "utf-8");
50
- parsed = processFile(file, raw);
51
- cache.set(file, parsed);
52
- }
53
-
54
- engine.preloadFile(getVirtualFile(file), parsed.content);
55
-
56
- } catch {}
57
- }
58
-
59
- // SCAN
60
- const detectedFn = new Set<string>();
61
- const detectedCmp = new Map<string, string>();
62
-
63
- for (const file of toScan) {
64
- try {
65
- const parsed = cache.get(file);
66
- if (!parsed) continue;
67
-
68
- const res = engine.scanFile(
69
- getVirtualFile(file),
70
- parsed.content,
71
- config
72
- );
73
-
74
- res.detectedFunctionWrappers.forEach(w => detectedFn.add(w));
75
- res.detectedComponentWrappers.forEach((v, k) => detectedCmp.set(k, v));
76
-
77
- } catch {}
78
- }
79
-
80
- // RESULT
81
- const events = engine.getAllEvents().sort();
82
-
83
- config.events = events;
84
-
85
- config.functionWrappers = mergeUnique(
86
- config.functionWrappers ?? [],
87
- [...detectedFn].map(name => ({ name })),
88
- w => w.name
89
- );
90
-
91
- config.wrappers = mergeUnique(
92
- config.wrappers ?? [],
93
- [...detectedCmp.entries()].map(([name, prop]) => ({ name, prop })),
94
- w => `${w.name}:${w.prop}`
95
- );
96
-
97
- await saveConfig(config);
98
-
99
- console.log(chalk.green(`Found ${events.length} events`));
100
- }
101
-
102
- function mergeUnique<T>(a: T[], b: T[], key: (v: T) => string): T[] {
103
- const map = new Map<string, T>();
104
- [...b, ...a].forEach(item => map.set(key(item), item));
105
- return [...map.values()];
106
- }
@@ -1,144 +0,0 @@
1
- import chokidar from "chokidar";
2
- import chalk from "chalk";
3
- import fs from "fs/promises";
4
- import fg from "fast-glob";
5
- import path from "path";
6
-
7
- import { loadConfig, saveConfig } from "../utils/config";
8
- import { EventraEngine } from "../engine/engine";
9
- import { processFile } from "../utils/processFile";
10
- import { getVirtualFile } from "../utils/getVirtualFile";
11
-
12
- export async function watch() {
13
- const config = await loadConfig();
14
- if (!config) return;
15
-
16
- console.log(chalk.blue("Watching...\n"));
17
-
18
- const engine = new EventraEngine(process.cwd());
19
- const fileDeps = new Map<string, Set<string>>();
20
- const cache = new Map<string, { content: string; deps: string[] }>();
21
- const watchedDeps = new Set<string>();
22
-
23
- const normalize = (f: string) =>
24
- path.resolve(f).replace(/\\/g, "/");
25
-
26
- // INITIAL LOAD
27
- const files = await fg(config.sync.include, {
28
- ignore: config.sync.exclude,
29
- absolute: true,
30
- });
31
-
32
- for (const file of files) {
33
- try {
34
- const abs = normalize(file);
35
- const raw = await fs.readFile(abs, "utf-8");
36
-
37
- const parsed = processFile(abs, raw);
38
-
39
- cache.set(abs, parsed);
40
-
41
- engine.preloadFile(getVirtualFile(abs), parsed.content);
42
- engine.updateFile(getVirtualFile(abs), parsed.content, config);
43
-
44
- fileDeps.set(abs, new Set(parsed.deps.map(d => normalize(d))));
45
-
46
- } catch {}
47
- }
48
-
49
- config.events = engine.getAllEvents().sort();
50
- await saveConfig(config);
51
-
52
- console.log(chalk.gray(`Initial: ${config.events.length}\n`));
53
-
54
- // WATCHER
55
- const watcher = chokidar.watch(
56
- [...files.map(normalize), ...watchedDeps],
57
- { ignoreInitial: true }
58
- );
59
-
60
- const queue = new Map<string, boolean>();
61
- let timer: NodeJS.Timeout | null = null;
62
-
63
- const run = async () => {
64
- const batch = [...queue.keys()];
65
- queue.clear();
66
-
67
- for (const file of batch) {
68
- try {
69
- const raw = await fs.readFile(file, "utf-8");
70
- const parsed = processFile(file, raw);
71
-
72
- cache.set(file, parsed);
73
-
74
- engine.updateFile(
75
- getVirtualFile(file),
76
- parsed.content,
77
- config
78
- );
79
-
80
- //dependency diff
81
- const abs = normalize(file);
82
-
83
- const prevDeps = fileDeps.get(abs) ?? new Set();
84
- const nextDeps = new Set(parsed.deps.map(d => normalize(d)));
85
-
86
- // add new deps
87
- for (const dep of nextDeps) {
88
- if (!prevDeps.has(dep)) {
89
- watcher.add(dep);
90
- watchedDeps.add(dep);
91
- }
92
- }
93
-
94
- // remove old deps
95
- for (const dep of prevDeps) {
96
- if (!nextDeps.has(dep)) {
97
- watcher.unwatch(dep);
98
- watchedDeps.delete(dep);
99
- }
100
- }
101
-
102
- fileDeps.set(abs, nextDeps);
103
-
104
- } catch {
105
- console.log(chalk.gray(`skip: ${file}`));
106
- }
107
- }
108
-
109
- const next = engine.getAllEvents().sort();
110
- config.events = next;
111
-
112
- await saveConfig(config);
113
-
114
- console.log(chalk.green(`Updated: ${next.length}`));
115
- };
116
-
117
- const schedule = (file: string) => {
118
- const abs = normalize(file);
119
- queue.set(abs, true);
120
-
121
- if (timer) clearTimeout(timer);
122
- timer = setTimeout(run, 200);
123
- };
124
-
125
- watcher
126
- .on("change", schedule)
127
- .on("add", schedule)
128
- .on("unlink", async (file) => {
129
- const abs = normalize(file);
130
-
131
- engine.removeFile(getVirtualFile(abs), config);
132
-
133
- const next = engine.getAllEvents().sort();
134
- config.events = next;
135
- await saveConfig(config);
136
-
137
- console.log(chalk.red(`Removed → ${next.length}`));
138
- });
139
-
140
- process.on("SIGINT", async () => {
141
- await watcher.close();
142
- process.exit(0);
143
- });
144
- }
@@ -1,12 +0,0 @@
1
- import path from "path";
2
-
3
- export function isExternalFile(file: string) {
4
- const normalized = path.resolve(file);
5
-
6
- return (
7
- normalized.includes(`${path.sep}node_modules${path.sep}`) ||
8
- normalized.includes(`${path.sep}dist${path.sep}`) ||
9
- normalized.includes(`${path.sep}build${path.sep}`) ||
10
- normalized.endsWith(".d.ts")
11
- );
12
- }
@@ -1,65 +0,0 @@
1
- import ts from "typescript";
2
-
3
- function isFn(node: ts.Node): node is ts.FunctionLikeDeclaration {
4
- return (
5
- ts.isFunctionDeclaration(node) ||
6
- ts.isMethodDeclaration(node) ||
7
- ts.isFunctionExpression(node) ||
8
- ts.isArrowFunction(node)
9
- );
10
- }
11
-
12
- function resolveFromSymbol(
13
- symbol: ts.Symbol,
14
- checker: ts.TypeChecker,
15
- seen = new Set<ts.Symbol>()
16
- ): ts.FunctionLikeDeclaration | null {
17
- if (seen.has(symbol)) return null;
18
- seen.add(symbol);
19
-
20
- if (symbol.flags & ts.SymbolFlags.Alias) {
21
- return resolveFromSymbol(checker.getAliasedSymbol(symbol), checker, seen);
22
- }
23
-
24
- for (const d of symbol.getDeclarations() ?? []) {
25
- if (isFn(d)) return d;
26
-
27
- if (ts.isVariableDeclaration(d) && d.initializer) {
28
- if (isFn(d.initializer)) return d.initializer;
29
-
30
- const s = checker.getSymbolAtLocation(d.initializer);
31
- if (s) {
32
- const r = resolveFromSymbol(s, checker, seen);
33
- if (r) return r;
34
- }
35
- }
36
- }
37
-
38
- return null;
39
- }
40
-
41
- export function resolveFunctionFromCall(
42
- expr: ts.Expression,
43
- checker: ts.TypeChecker
44
- ): ts.FunctionLikeDeclaration | null {
45
- // foo.bar()
46
- if (ts.isPropertyAccessExpression(expr)) {
47
- const s = checker.getSymbolAtLocation(expr.name);
48
- if (s) {
49
- const r = resolveFromSymbol(s, checker);
50
- if (r) return r;
51
- }
52
- }
53
-
54
- // direct call
55
- const type = checker.getTypeAtLocation(expr);
56
- for (const sig of type.getCallSignatures()) {
57
- const d = sig.getDeclaration();
58
- if (d && isFn(d)) return d;
59
- }
60
-
61
- const s = checker.getSymbolAtLocation(expr);
62
- if (s) return resolveFromSymbol(s, checker);
63
-
64
- return null;
65
- }