@eventra_dev/eventra-cli 0.3.5 → 0.3.9
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/dist/analysis/engine/analyzeFile.js +12 -0
- package/dist/analysis/resolver/callResolver.js +4 -0
- package/dist/analysis/resolver/exportResolver.js +3 -0
- package/dist/analysis/symbols/wrapperRegistry.js +9 -15
- package/dist/cli/check.js +29 -47
- package/dist/cli/init.js +1 -0
- package/dist/cli/sync.js +5 -3
- package/dist/cli/watch.js +20 -33
- package/dist/compiler/compilerContext.js +71 -49
- package/dist/config/config.js +13 -2
- package/dist/core/EventraEngine.js +89 -37
- package/dist/core/projectScanner.js +9 -15
- package/dist/core/scanResults.js +18 -0
- package/dist/index.js +5 -1
- package/package.json +2 -2
- package/dist/analysis/engine/recursiveWrapperAnalyzer.js +0 -76
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.indexSourceFile = indexSourceFile;
|
|
4
|
+
exports.extractFromIndex = extractFromIndex;
|
|
5
|
+
const scanner_1 = require("../scanner/scanner");
|
|
6
|
+
const extractor_1 = require("../extractor/extractor");
|
|
7
|
+
function indexSourceFile(source, checker, wrapperRegistry) {
|
|
8
|
+
return (0, scanner_1.scanSource)(source, checker, wrapperRegistry);
|
|
9
|
+
}
|
|
10
|
+
function extractFromIndex(index, config, checker, wrapperRegistry, evaluationCache, resolvedCallCache, returnPropagationCache, resolvedExportCache) {
|
|
11
|
+
return (0, extractor_1.extractEvents)(index, checker, config, evaluationCache, resolvedExportCache, resolvedCallCache, returnPropagationCache, wrapperRegistry);
|
|
12
|
+
}
|
|
@@ -22,6 +22,10 @@ function resolveDeclaration(declaration) {
|
|
|
22
22
|
if (typescript_1.default.isPropertyDeclaration(declaration) && declaration.initializer && isFunctionLike(declaration.initializer)) {
|
|
23
23
|
return declaration.initializer;
|
|
24
24
|
}
|
|
25
|
+
// { trackFeature() {} }
|
|
26
|
+
if (typescript_1.default.isPropertyAssignment(declaration) && declaration.initializer && isFunctionLike(declaration.initializer)) {
|
|
27
|
+
return declaration.initializer;
|
|
28
|
+
}
|
|
25
29
|
return null;
|
|
26
30
|
}
|
|
27
31
|
function getExpressionSymbol(expression, checker) {
|
|
@@ -16,6 +16,9 @@ function isConcreteDeclaration(node) {
|
|
|
16
16
|
|| typescript_1.default.isPropertyDeclaration(node));
|
|
17
17
|
}
|
|
18
18
|
function resolveExportedSymbol(symbol, checker, cache, visited) {
|
|
19
|
+
if (typeof symbol.getDeclarations !== "function") {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
19
22
|
const seen = visited ?? new Set();
|
|
20
23
|
const cached = cache.get(symbol);
|
|
21
24
|
if (cached !== undefined) {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.WrapperRegistry = void 0;
|
|
7
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
8
4
|
const exportResolver_1 = require("../resolver/exportResolver");
|
|
5
|
+
const symbolUtils_1 = require("./symbolUtils");
|
|
9
6
|
function isSymbol(value) {
|
|
10
|
-
return "
|
|
7
|
+
return typeof value.getDeclarations === "function";
|
|
11
8
|
}
|
|
12
9
|
class WrapperRegistry {
|
|
13
10
|
checker;
|
|
@@ -17,6 +14,12 @@ class WrapperRegistry {
|
|
|
17
14
|
this.checker = checker;
|
|
18
15
|
this.exportCache = exportCache;
|
|
19
16
|
}
|
|
17
|
+
setChecker(checker) {
|
|
18
|
+
this.checker = checker;
|
|
19
|
+
}
|
|
20
|
+
getChecker() {
|
|
21
|
+
return this.checker;
|
|
22
|
+
}
|
|
20
23
|
normalize(symbol) {
|
|
21
24
|
return ((0, exportResolver_1.resolveExportedSymbol)(symbol, this.checker, this.exportCache) ?? symbol);
|
|
22
25
|
}
|
|
@@ -27,16 +30,7 @@ class WrapperRegistry {
|
|
|
27
30
|
if (isSymbol(target)) {
|
|
28
31
|
return this.wrappers.get(this.normalize(target));
|
|
29
32
|
}
|
|
30
|
-
|
|
31
|
-
if (typescript_1.default.isFunctionDeclaration(target) && target.name) {
|
|
32
|
-
symbol = this.checker.getSymbolAtLocation(target.name);
|
|
33
|
-
}
|
|
34
|
-
else if (typescript_1.default.isMethodDeclaration(target) && typescript_1.default.isIdentifier(target.name)) {
|
|
35
|
-
symbol = this.checker.getSymbolAtLocation(target.name);
|
|
36
|
-
}
|
|
37
|
-
else if (target.parent && typescript_1.default.isVariableDeclaration(target.parent) && typescript_1.default.isIdentifier(target.parent.name)) {
|
|
38
|
-
symbol = this.checker.getSymbolAtLocation(target.parent.name);
|
|
39
|
-
}
|
|
33
|
+
const symbol = (0, symbolUtils_1.getFunctionSymbol)(target, this.checker);
|
|
40
34
|
if (!symbol) {
|
|
41
35
|
return undefined;
|
|
42
36
|
}
|
package/dist/cli/check.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -40,31 +7,46 @@ exports.check = check;
|
|
|
40
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
8
|
const config_1 = require("../config/config");
|
|
42
9
|
const projectScanner_1 = require("../core/projectScanner");
|
|
10
|
+
const scanResults_1 = require("../core/scanResults");
|
|
11
|
+
function diffSets(found, known) {
|
|
12
|
+
return {
|
|
13
|
+
added: [...found].filter((e) => !known.has(e)),
|
|
14
|
+
removed: [...known].filter((e) => !found.has(e)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
43
17
|
async function check({ fix = false }) {
|
|
44
18
|
const config = await (0, config_1.loadConfig)();
|
|
45
19
|
if (!config) {
|
|
46
20
|
console.log(chalk_1.default.red("eventra.json not found"));
|
|
47
21
|
process.exit(1);
|
|
48
22
|
}
|
|
49
|
-
console.log(chalk_1.default.blue("Checking events..."));
|
|
23
|
+
console.log(chalk_1.default.blue("Checking events and function wrappers..."));
|
|
50
24
|
const { engine } = await (0, projectScanner_1.scanProject)(config);
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
25
|
+
const foundEvents = new Set(engine.getAllEvents());
|
|
26
|
+
const knownEvents = new Set(config.events ?? []);
|
|
27
|
+
const eventDiff = diffSets(foundEvents, knownEvents);
|
|
28
|
+
const foundWrappers = new Set(engine.getAllFunctionWrappers());
|
|
29
|
+
const knownWrappers = new Set(config.functionWrappers?.map((w) => w.name) ?? []);
|
|
30
|
+
const wrapperDiff = diffSets(foundWrappers, knownWrappers);
|
|
55
31
|
if (fix) {
|
|
56
|
-
|
|
57
|
-
...config,
|
|
58
|
-
events: [...found].sort(),
|
|
59
|
-
};
|
|
60
|
-
const { saveConfig } = await Promise.resolve().then(() => __importStar(require("../config/config")));
|
|
61
|
-
await saveConfig(nextConfig);
|
|
32
|
+
await (0, config_1.saveConfig)((0, scanResults_1.buildConfigFromScan)(config, engine));
|
|
62
33
|
console.log(chalk_1.default.green("Synced"));
|
|
63
34
|
return;
|
|
64
35
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
let hasDiff = false;
|
|
37
|
+
if (eventDiff.added.length || eventDiff.removed.length) {
|
|
38
|
+
hasDiff = true;
|
|
39
|
+
console.log(chalk_1.default.blue("\nEvents:"));
|
|
40
|
+
eventDiff.added.forEach((e) => console.log(chalk_1.default.red(`+ ${e}`)));
|
|
41
|
+
eventDiff.removed.forEach((e) => console.log(chalk_1.default.yellow(`- ${e}`)));
|
|
42
|
+
}
|
|
43
|
+
if (wrapperDiff.added.length || wrapperDiff.removed.length) {
|
|
44
|
+
hasDiff = true;
|
|
45
|
+
console.log(chalk_1.default.blue("\nFunction wrappers:"));
|
|
46
|
+
wrapperDiff.added.forEach((w) => console.log(chalk_1.default.red(`+ ${w}`)));
|
|
47
|
+
wrapperDiff.removed.forEach((w) => console.log(chalk_1.default.yellow(`- ${w}`)));
|
|
48
|
+
}
|
|
49
|
+
if (hasDiff) {
|
|
68
50
|
process.exit(1);
|
|
69
51
|
}
|
|
70
52
|
console.log(chalk_1.default.green("All good"));
|
package/dist/cli/init.js
CHANGED
package/dist/cli/sync.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.sync = sync;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const config_1 = require("../config/config");
|
|
9
9
|
const projectScanner_1 = require("../core/projectScanner");
|
|
10
|
+
const scanResults_1 = require("../core/scanResults");
|
|
10
11
|
async function sync() {
|
|
11
12
|
const config = await (0, config_1.loadConfig)();
|
|
12
13
|
if (!config) {
|
|
@@ -15,7 +16,8 @@ async function sync() {
|
|
|
15
16
|
}
|
|
16
17
|
console.log(chalk_1.default.blue("Scanning..."));
|
|
17
18
|
const { engine } = await (0, projectScanner_1.scanProject)(config);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
await (0, scanResults_1.persistScanResults)(config, engine);
|
|
20
|
+
const events = engine.getAllEvents();
|
|
21
|
+
const wrappers = engine.getAllFunctionWrappers();
|
|
22
|
+
console.log(chalk_1.default.green(`Found ${events.length} events, ${wrappers.length} function wrappers`));
|
|
21
23
|
}
|
package/dist/cli/watch.js
CHANGED
|
@@ -11,6 +11,7 @@ const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const config_1 = require("../config/config");
|
|
13
13
|
const EventraEngine_1 = require("../core/EventraEngine");
|
|
14
|
+
const scanResults_1 = require("../core/scanResults");
|
|
14
15
|
const processFile_1 = require("../filesystem/processFile");
|
|
15
16
|
const getVirtualFile_1 = require("../filesystem/getVirtualFile");
|
|
16
17
|
async function watch() {
|
|
@@ -24,10 +25,10 @@ async function watch() {
|
|
|
24
25
|
const fileDeps = new Map();
|
|
25
26
|
const cache = new Map();
|
|
26
27
|
const watchedDeps = new Set();
|
|
28
|
+
const trackedFiles = new Set();
|
|
27
29
|
const normalize = (file) => {
|
|
28
30
|
return path_1.default.resolve(file).replace(/\\/g, "/");
|
|
29
31
|
};
|
|
30
|
-
// INITIAL LOAD
|
|
31
32
|
const files = await (0, fast_glob_1.default)(config.sync.include, {
|
|
32
33
|
ignore: config.sync.exclude,
|
|
33
34
|
absolute: true,
|
|
@@ -39,23 +40,21 @@ async function watch() {
|
|
|
39
40
|
const raw = await promises_1.default.readFile(abs, "utf-8");
|
|
40
41
|
const parsed = await (0, processFile_1.processFile)(abs, raw);
|
|
41
42
|
cache.set(abs, parsed);
|
|
43
|
+
trackedFiles.add(abs);
|
|
42
44
|
await engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content);
|
|
43
|
-
|
|
44
|
-
fileDeps.set(abs, new Set(parsed.dependencies.map(dep => normalize(dep))));
|
|
45
|
+
fileDeps.set(abs, new Set(parsed.dependencies.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep)))));
|
|
45
46
|
}
|
|
46
47
|
catch {
|
|
47
48
|
// ignore invalid files
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
engine.endPreload();
|
|
51
|
-
|
|
52
|
-
await (0,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// WATCHER
|
|
58
|
-
const watcher = chokidar_1.default.watch([...files.map(normalize), ...watchedDeps], { ignoreInitial: true });
|
|
52
|
+
await engine.runFullAnalysis([...trackedFiles].map((f) => (0, getVirtualFile_1.getVirtualFile)(f)), config);
|
|
53
|
+
await (0, scanResults_1.persistScanResults)(config, engine);
|
|
54
|
+
const initialEvents = engine.getAllEvents();
|
|
55
|
+
const initialWrappers = engine.getAllFunctionWrappers();
|
|
56
|
+
console.log(chalk_1.default.gray(`Initial: ${initialEvents.length} events, ${initialWrappers.length} wrappers\n`));
|
|
57
|
+
const watcher = chokidar_1.default.watch([...trackedFiles, ...watchedDeps], { ignoreInitial: true });
|
|
59
58
|
const queue = new Map();
|
|
60
59
|
let timer = null;
|
|
61
60
|
const run = async () => {
|
|
@@ -66,24 +65,20 @@ async function watch() {
|
|
|
66
65
|
const raw = await promises_1.default.readFile(file, "utf-8");
|
|
67
66
|
const parsed = await (0, processFile_1.processFile)(file, raw);
|
|
68
67
|
cache.set(file, parsed);
|
|
68
|
+
trackedFiles.add(file);
|
|
69
69
|
await engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
|
|
70
|
-
// dependency diff
|
|
71
70
|
const abs = normalize(file);
|
|
72
71
|
const prevDeps = fileDeps.get(abs) ?? new Set();
|
|
73
|
-
const nextDeps = new Set(parsed.dependencies.map(dep => normalize(dep)));
|
|
74
|
-
// add deps
|
|
72
|
+
const nextDeps = new Set(parsed.dependencies.map((dep) => normalize(path_1.default.resolve(path_1.default.dirname(abs), dep))));
|
|
75
73
|
for (const dep of nextDeps) {
|
|
76
|
-
if (prevDeps.has(dep))
|
|
74
|
+
if (prevDeps.has(dep))
|
|
77
75
|
continue;
|
|
78
|
-
}
|
|
79
76
|
watcher.add(dep);
|
|
80
77
|
watchedDeps.add(dep);
|
|
81
78
|
}
|
|
82
|
-
// remove deps
|
|
83
79
|
for (const dep of prevDeps) {
|
|
84
|
-
if (nextDeps.has(dep))
|
|
80
|
+
if (nextDeps.has(dep))
|
|
85
81
|
continue;
|
|
86
|
-
}
|
|
87
82
|
watcher.unwatch(dep);
|
|
88
83
|
watchedDeps.delete(dep);
|
|
89
84
|
}
|
|
@@ -93,19 +88,14 @@ async function watch() {
|
|
|
93
88
|
console.log(chalk_1.default.gray(`skip: ${file}`));
|
|
94
89
|
}
|
|
95
90
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
...config,
|
|
99
|
-
events: nextEvents,
|
|
100
|
-
});
|
|
101
|
-
console.log(chalk_1.default.green(`Updated: ${nextEvents.length}`));
|
|
91
|
+
await (0, scanResults_1.persistScanResults)(config, engine);
|
|
92
|
+
console.log(chalk_1.default.green(`Updated: ${engine.getAllEvents().length} events, ${engine.getAllFunctionWrappers().length} wrappers`));
|
|
102
93
|
};
|
|
103
94
|
const schedule = (file) => {
|
|
104
95
|
const abs = normalize(file);
|
|
105
96
|
queue.set(abs, true);
|
|
106
|
-
if (timer)
|
|
97
|
+
if (timer)
|
|
107
98
|
clearTimeout(timer);
|
|
108
|
-
}
|
|
109
99
|
timer = setTimeout(run, 200);
|
|
110
100
|
};
|
|
111
101
|
watcher
|
|
@@ -113,13 +103,10 @@ async function watch() {
|
|
|
113
103
|
.on("add", schedule)
|
|
114
104
|
.on("unlink", async (file) => {
|
|
115
105
|
const abs = normalize(file);
|
|
106
|
+
trackedFiles.delete(abs);
|
|
116
107
|
await engine.removeFile((0, getVirtualFile_1.getVirtualFile)(abs), config);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
...config,
|
|
120
|
-
events: nextEvents,
|
|
121
|
-
});
|
|
122
|
-
console.log(chalk_1.default.red(`Removed → ${nextEvents.length}`));
|
|
108
|
+
await (0, scanResults_1.persistScanResults)(config, engine);
|
|
109
|
+
console.log(chalk_1.default.red(`Removed → ${engine.getAllEvents().length} events, ${engine.getAllFunctionWrappers().length} wrappers`));
|
|
123
110
|
});
|
|
124
111
|
process.on("SIGINT", async () => {
|
|
125
112
|
await watcher.close();
|
|
@@ -14,11 +14,12 @@ class CompilerContext {
|
|
|
14
14
|
registry = new documentRegistry_1.DocumentRegistry();
|
|
15
15
|
compilerOptions;
|
|
16
16
|
host;
|
|
17
|
-
|
|
17
|
+
program = typescript_1.default.createProgram([], {});
|
|
18
18
|
rootNames = new Set();
|
|
19
19
|
constructor(rootDir) {
|
|
20
20
|
this.rootDir = rootDir;
|
|
21
|
-
const
|
|
21
|
+
const localTsconfig = path_1.default.join(rootDir, "tsconfig.json");
|
|
22
|
+
const configPath = fs_1.default.existsSync(localTsconfig) ? localTsconfig : undefined;
|
|
22
23
|
let compilerOptions = {
|
|
23
24
|
target: typescript_1.default.ScriptTarget.ESNext,
|
|
24
25
|
module: typescript_1.default.ModuleKind.ESNext,
|
|
@@ -33,7 +34,7 @@ class CompilerContext {
|
|
|
33
34
|
esModuleInterop: true,
|
|
34
35
|
resolveJsonModule: true,
|
|
35
36
|
allowNonTsExtensions: true,
|
|
36
|
-
incremental:
|
|
37
|
+
incremental: false,
|
|
37
38
|
};
|
|
38
39
|
if (configPath) {
|
|
39
40
|
const config = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
@@ -52,30 +53,59 @@ class CompilerContext {
|
|
|
52
53
|
this.compilerOptions = compilerOptions;
|
|
53
54
|
this.host = typescript_1.default.createCompilerHost(this.compilerOptions, true);
|
|
54
55
|
this.patchCompilerHost();
|
|
55
|
-
this.
|
|
56
|
+
this.rebuildProgram();
|
|
56
57
|
}
|
|
57
58
|
normalize(fileName) {
|
|
58
59
|
return this.registry.normalize(fileName);
|
|
59
60
|
}
|
|
61
|
+
resolveLanguageVersion(languageVersionOrOptions) {
|
|
62
|
+
if (typeof languageVersionOrOptions === "object") {
|
|
63
|
+
return languageVersionOrOptions.languageVersion;
|
|
64
|
+
}
|
|
65
|
+
return languageVersionOrOptions;
|
|
66
|
+
}
|
|
67
|
+
attachVersion(sourceFile, fileName) {
|
|
68
|
+
const normalized = this.normalize(fileName);
|
|
69
|
+
sourceFile.version =
|
|
70
|
+
this.registry.getVersion(normalized);
|
|
71
|
+
return sourceFile;
|
|
72
|
+
}
|
|
73
|
+
createSourceFileFromRegistry(fileName, languageVersion) {
|
|
74
|
+
const normalized = this.normalize(fileName);
|
|
75
|
+
const snapshot = this.registry.getSnapshot(normalized);
|
|
76
|
+
if (!snapshot) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const sourceFile = typescript_1.default.createSourceFile(normalized, snapshot.getText(0, snapshot.getLength()), languageVersion, true, this.getScriptKind(normalized));
|
|
80
|
+
return this.attachVersion(sourceFile, normalized);
|
|
81
|
+
}
|
|
60
82
|
patchCompilerHost() {
|
|
61
|
-
const originalGetSourceFile = this.host.getSourceFile;
|
|
62
|
-
this.host.getSourceFile = (fileName,
|
|
83
|
+
const originalGetSourceFile = this.host.getSourceFile.bind(this.host);
|
|
84
|
+
this.host.getSourceFile = (fileName, languageVersionOrOptions, onError, shouldCreateNewSourceFile) => {
|
|
85
|
+
const languageVersion = this.resolveLanguageVersion(languageVersionOrOptions);
|
|
63
86
|
const normalized = this.normalize(fileName);
|
|
64
87
|
this.registry.ensure(normalized);
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
return
|
|
88
|
+
const fromRegistry = this.createSourceFileFromRegistry(normalized, languageVersion);
|
|
89
|
+
if (fromRegistry) {
|
|
90
|
+
return fromRegistry;
|
|
91
|
+
}
|
|
92
|
+
const fromDisk = originalGetSourceFile(normalized, languageVersionOrOptions, onError, shouldCreateNewSourceFile);
|
|
93
|
+
if (!fromDisk) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
if (!this.registry.has(normalized)) {
|
|
97
|
+
this.registry.update(normalized, fromDisk.getFullText());
|
|
68
98
|
}
|
|
69
|
-
return
|
|
99
|
+
return this.attachVersion(fromDisk, normalized);
|
|
70
100
|
};
|
|
71
101
|
this.host.readFile = (fileName) => {
|
|
72
102
|
const normalized = this.normalize(fileName);
|
|
73
103
|
this.registry.ensure(normalized);
|
|
74
|
-
return
|
|
104
|
+
return this.registry.getContent(normalized) ?? fs_1.default.readFileSync(normalized, "utf8");
|
|
75
105
|
};
|
|
76
106
|
this.host.fileExists = (fileName) => {
|
|
77
107
|
const normalized = this.normalize(fileName);
|
|
78
|
-
return
|
|
108
|
+
return this.registry.has(normalized) || fs_1.default.existsSync(normalized);
|
|
79
109
|
};
|
|
80
110
|
this.host.getCanonicalFileName = (f) => this.normalize(f);
|
|
81
111
|
this.host.useCaseSensitiveFileNames = () => true;
|
|
@@ -83,64 +113,62 @@ class CompilerContext {
|
|
|
83
113
|
this.host.getNewLine = () => "\n";
|
|
84
114
|
}
|
|
85
115
|
getScriptKind(fileName) {
|
|
86
|
-
if (fileName.endsWith(".tsx"))
|
|
116
|
+
if (fileName.endsWith(".tsx"))
|
|
87
117
|
return typescript_1.default.ScriptKind.TSX;
|
|
88
|
-
|
|
89
|
-
if (fileName.endsWith(".ts")) {
|
|
118
|
+
if (fileName.endsWith(".ts"))
|
|
90
119
|
return typescript_1.default.ScriptKind.TS;
|
|
91
|
-
|
|
92
|
-
if (fileName.endsWith(".jsx")) {
|
|
120
|
+
if (fileName.endsWith(".jsx"))
|
|
93
121
|
return typescript_1.default.ScriptKind.JSX;
|
|
94
|
-
|
|
95
|
-
if (fileName.endsWith(".js")) {
|
|
122
|
+
if (fileName.endsWith(".js"))
|
|
96
123
|
return typescript_1.default.ScriptKind.JS;
|
|
97
|
-
|
|
98
|
-
if (fileName.endsWith(".json")) {
|
|
124
|
+
if (fileName.endsWith(".json"))
|
|
99
125
|
return typescript_1.default.ScriptKind.JSON;
|
|
100
|
-
}
|
|
101
126
|
return typescript_1.default.ScriptKind.Unknown;
|
|
102
127
|
}
|
|
103
|
-
|
|
104
|
-
this.
|
|
128
|
+
rebuildProgram() {
|
|
129
|
+
this.program = typescript_1.default.createProgram([...this.rootNames], this.compilerOptions, this.host);
|
|
130
|
+
}
|
|
131
|
+
/** Register file content without rebuilding the TS program (batch via {@link flushUpdates}). */
|
|
132
|
+
stageFile(fileName, content) {
|
|
133
|
+
const normalized = this.normalize(fileName);
|
|
134
|
+
this.registry.update(normalized, content);
|
|
135
|
+
this.rootNames.add(normalized);
|
|
136
|
+
}
|
|
137
|
+
flushUpdates() {
|
|
138
|
+
this.rebuildProgram();
|
|
105
139
|
}
|
|
106
140
|
async updateFile(fileName, content) {
|
|
107
141
|
const normalized = this.normalize(fileName);
|
|
108
142
|
const processed = await (0, processFile_1.processFile)(normalized, content);
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
this.rebuild();
|
|
143
|
+
this.stageFile(processed.fileName, processed.content);
|
|
144
|
+
this.rebuildProgram();
|
|
112
145
|
}
|
|
113
146
|
removeFile(fileName) {
|
|
114
147
|
const normalized = this.normalize(fileName);
|
|
115
148
|
this.registry.remove(normalized);
|
|
116
149
|
this.rootNames.delete(normalized);
|
|
117
|
-
this.
|
|
118
|
-
}
|
|
119
|
-
invalidate() {
|
|
120
|
-
this.registry.invalidate();
|
|
121
|
-
this.rebuild();
|
|
150
|
+
this.rebuildProgram();
|
|
122
151
|
}
|
|
123
152
|
getProgram() {
|
|
124
|
-
return this.
|
|
153
|
+
return this.program;
|
|
125
154
|
}
|
|
126
155
|
getChecker() {
|
|
127
|
-
return this.
|
|
156
|
+
return this.program.getTypeChecker();
|
|
128
157
|
}
|
|
129
158
|
getSourceFile(fileName) {
|
|
130
|
-
return this.
|
|
159
|
+
return this.program.getSourceFile(this.normalize(fileName));
|
|
131
160
|
}
|
|
132
161
|
getSemanticDiagnostics(fileName) {
|
|
133
|
-
const program = this.builder.getProgram();
|
|
134
162
|
if (!fileName) {
|
|
135
|
-
return typescript_1.default.getPreEmitDiagnostics(program);
|
|
163
|
+
return typescript_1.default.getPreEmitDiagnostics(this.program);
|
|
136
164
|
}
|
|
137
165
|
const sourceFile = this.getSourceFile(fileName);
|
|
138
166
|
if (!sourceFile) {
|
|
139
167
|
return [];
|
|
140
168
|
}
|
|
141
169
|
return [
|
|
142
|
-
...program.getSemanticDiagnostics(sourceFile),
|
|
143
|
-
...program.getSyntacticDiagnostics(sourceFile),
|
|
170
|
+
...this.program.getSemanticDiagnostics(sourceFile),
|
|
171
|
+
...this.program.getSyntacticDiagnostics(sourceFile),
|
|
144
172
|
];
|
|
145
173
|
}
|
|
146
174
|
resolveModule(moduleName, fromFile) {
|
|
@@ -162,20 +190,14 @@ class CompilerContext {
|
|
|
162
190
|
continue;
|
|
163
191
|
}
|
|
164
192
|
const resolved = this.resolveModule(specifier.text, source.fileName);
|
|
165
|
-
if (
|
|
166
|
-
|
|
193
|
+
if (resolved) {
|
|
194
|
+
modules.add(this.normalize(resolved));
|
|
167
195
|
}
|
|
168
|
-
modules.add(this.normalize(resolved));
|
|
169
196
|
}
|
|
170
|
-
return [
|
|
171
|
-
...modules,
|
|
172
|
-
];
|
|
197
|
+
return [...modules];
|
|
173
198
|
}
|
|
174
199
|
getAllSourceFiles() {
|
|
175
|
-
return this.
|
|
176
|
-
}
|
|
177
|
-
getRealFileName(fileName) {
|
|
178
|
-
return fileName;
|
|
200
|
+
return this.program.getSourceFiles().filter((sf) => !sf.isDeclarationFile);
|
|
179
201
|
}
|
|
180
202
|
}
|
|
181
203
|
exports.CompilerContext = CompilerContext;
|
package/dist/config/config.js
CHANGED
|
@@ -13,10 +13,21 @@ exports.CONFIG_NAME = "eventra.json";
|
|
|
13
13
|
const DEFAULT_INCLUDE = ["**/*.{ts,tsx,js,jsx}"];
|
|
14
14
|
const DEFAULT_EXCLUDE = ["node_modules", "dist", ".next", ".git"];
|
|
15
15
|
function normalizeConfig(config) {
|
|
16
|
+
const functionWrappers = (config.functionWrappers ?? [])
|
|
17
|
+
.filter((w) => w?.name)
|
|
18
|
+
.map((w) => ({ name: w.name }));
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
const uniqueWrappers = functionWrappers.filter((w) => {
|
|
21
|
+
if (seen.has(w.name))
|
|
22
|
+
return false;
|
|
23
|
+
seen.add(w.name);
|
|
24
|
+
return true;
|
|
25
|
+
});
|
|
16
26
|
return {
|
|
17
27
|
apiKey: config.apiKey ?? "",
|
|
18
28
|
endpoint: config.endpoint ?? "",
|
|
19
|
-
events: config.events ?? [],
|
|
29
|
+
events: [...(config.events ?? [])].sort(),
|
|
30
|
+
functionWrappers: uniqueWrappers.sort((a, b) => a.name.localeCompare(b.name)),
|
|
20
31
|
sync: {
|
|
21
32
|
include: config.sync?.include ?? DEFAULT_INCLUDE,
|
|
22
33
|
exclude: config.sync?.exclude ?? DEFAULT_EXCLUDE,
|
|
@@ -25,7 +36,7 @@ function normalizeConfig(config) {
|
|
|
25
36
|
}
|
|
26
37
|
async function loadConfig() {
|
|
27
38
|
const configPath = path_1.default.join(process.cwd(), exports.CONFIG_NAME);
|
|
28
|
-
if (!await fs_extra_1.default.pathExists(configPath)) {
|
|
39
|
+
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
29
40
|
return null;
|
|
30
41
|
}
|
|
31
42
|
try {
|
|
@@ -14,8 +14,9 @@ const resolvedCallCache_1 = require("../analysis/cache/resolvedCallCache");
|
|
|
14
14
|
const resolvedExportCache_1 = require("../analysis/cache/resolvedExportCache");
|
|
15
15
|
const returnPropagationCache_1 = require("../analysis/cache/returnPropagationCache");
|
|
16
16
|
const symbolInvalidation_1 = require("../analysis/cache/symbolInvalidation");
|
|
17
|
+
const analyzeFile_1 = require("../analysis/engine/analyzeFile");
|
|
17
18
|
const wrapperRegistry_1 = require("../analysis/symbols/wrapperRegistry");
|
|
18
|
-
const
|
|
19
|
+
const processFile_1 = require("../filesystem/processFile");
|
|
19
20
|
const EMPTY_RESULT = () => ({
|
|
20
21
|
events: new Set(),
|
|
21
22
|
detectedFunctionWrappers: new Set(),
|
|
@@ -29,11 +30,13 @@ class EventraEngine {
|
|
|
29
30
|
returnPropagationCache = new returnPropagationCache_1.ReturnPropagationCache();
|
|
30
31
|
resolvedExportCache = new resolvedExportCache_1.ResolvedExportCache();
|
|
31
32
|
wrapperRegistry;
|
|
33
|
+
fileIndices = new Map();
|
|
32
34
|
fileResults = new Map();
|
|
33
35
|
normalizedPathCache = new Map();
|
|
34
36
|
isPreloading = false;
|
|
35
37
|
lastConfig = {
|
|
36
38
|
events: [],
|
|
39
|
+
functionWrappers: [],
|
|
37
40
|
sync: {
|
|
38
41
|
include: [],
|
|
39
42
|
exclude: [],
|
|
@@ -43,7 +46,7 @@ class EventraEngine {
|
|
|
43
46
|
this.compiler = new compilerContext_1.CompilerContext(rootDir);
|
|
44
47
|
this.wrapperRegistry = new wrapperRegistry_1.WrapperRegistry(this.compiler.getChecker(), this.resolvedExportCache);
|
|
45
48
|
this.scheduler = new scheduler_1.Scheduler(async (updates) => {
|
|
46
|
-
|
|
49
|
+
const affected = new Set();
|
|
47
50
|
for (const [file, content] of updates) {
|
|
48
51
|
const existing = this.compiler.getSourceFile(file);
|
|
49
52
|
if (existing) {
|
|
@@ -51,27 +54,17 @@ class EventraEngine {
|
|
|
51
54
|
}
|
|
52
55
|
await this.compiler.updateFile(file, content);
|
|
53
56
|
this.updateImportGraph(file);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const affected = new Set();
|
|
57
|
-
for (const file of updates.keys()) {
|
|
58
|
-
const dependents = this.importGraph.collectDependents(file);
|
|
59
|
-
for (const dependent of dependents) {
|
|
57
|
+
affected.add(file);
|
|
58
|
+
for (const dependent of this.importGraph.collectDependents(file)) {
|
|
60
59
|
affected.add(dependent);
|
|
61
60
|
}
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
for (const file of affected) {
|
|
65
|
-
await this.analyzeFile(file, this.lastConfig);
|
|
66
|
-
}
|
|
62
|
+
await this.reanalyzeFiles([...affected], this.lastConfig);
|
|
67
63
|
});
|
|
68
64
|
}
|
|
69
65
|
beginPreload() {
|
|
70
66
|
this.isPreloading = true;
|
|
71
67
|
}
|
|
72
|
-
endPreload() {
|
|
73
|
-
this.isPreloading = false;
|
|
74
|
-
}
|
|
75
68
|
normalize(fileName) {
|
|
76
69
|
const cached = this.normalizedPathCache.get(fileName);
|
|
77
70
|
if (cached) {
|
|
@@ -113,45 +106,92 @@ class EventraEngine {
|
|
|
113
106
|
throw new Error("preload phase not active");
|
|
114
107
|
}
|
|
115
108
|
const normalized = this.normalize(fileName);
|
|
116
|
-
await
|
|
117
|
-
this.
|
|
109
|
+
const processed = await (0, processFile_1.processFile)(normalized, content);
|
|
110
|
+
this.compiler.stageFile(processed.fileName, processed.content);
|
|
111
|
+
}
|
|
112
|
+
endPreload() {
|
|
113
|
+
this.isPreloading = false;
|
|
114
|
+
this.compiler.flushUpdates();
|
|
115
|
+
for (const sourceFile of this.compiler.getAllSourceFiles()) {
|
|
116
|
+
this.updateImportGraph(sourceFile.fileName);
|
|
117
|
+
}
|
|
118
118
|
}
|
|
119
119
|
async updateFile(fileName, content, config) {
|
|
120
120
|
this.lastConfig = config;
|
|
121
121
|
const normalized = this.normalize(fileName);
|
|
122
122
|
await this.scheduler.enqueue(normalized, content);
|
|
123
123
|
}
|
|
124
|
+
refreshChecker() {
|
|
125
|
+
this.wrapperRegistry.setChecker(this.compiler.getChecker());
|
|
126
|
+
}
|
|
127
|
+
async runFullAnalysis(fileNames, config) {
|
|
128
|
+
this.lastConfig = config;
|
|
129
|
+
this.refreshChecker();
|
|
130
|
+
const normalized = fileNames.map((f) => this.normalize(f));
|
|
131
|
+
for (const file of normalized) {
|
|
132
|
+
this.indexFile(file, config);
|
|
133
|
+
}
|
|
134
|
+
for (const file of normalized) {
|
|
135
|
+
this.extractFile(file, config);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async reanalyzeFiles(fileNames, config) {
|
|
139
|
+
this.lastConfig = config;
|
|
140
|
+
this.refreshChecker();
|
|
141
|
+
const normalized = fileNames.map((f) => this.normalize(f));
|
|
142
|
+
for (const file of normalized) {
|
|
143
|
+
this.indexFile(file, config);
|
|
144
|
+
}
|
|
145
|
+
for (const file of normalized) {
|
|
146
|
+
this.extractFile(file, config);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
indexFile(fileName, _config) {
|
|
150
|
+
const normalized = this.normalize(fileName);
|
|
151
|
+
const source = this.compiler.getSourceFile(normalized);
|
|
152
|
+
if (!source) {
|
|
153
|
+
this.fileIndices.delete(normalized);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const index = (0, analyzeFile_1.indexSourceFile)(source, this.compiler.getChecker(), this.wrapperRegistry);
|
|
157
|
+
this.fileIndices.set(normalized, index);
|
|
158
|
+
}
|
|
159
|
+
extractFile(fileName, config) {
|
|
160
|
+
const normalized = this.normalize(fileName);
|
|
161
|
+
const index = this.fileIndices.get(normalized);
|
|
162
|
+
if (!index) {
|
|
163
|
+
this.fileResults.delete(normalized);
|
|
164
|
+
return EMPTY_RESULT();
|
|
165
|
+
}
|
|
166
|
+
const result = (0, analyzeFile_1.extractFromIndex)(index, config, this.compiler.getChecker(), this.wrapperRegistry, this.evaluationCache, this.resolvedCallCache, this.returnPropagationCache, this.resolvedExportCache);
|
|
167
|
+
this.fileResults.set(normalized, result);
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
124
170
|
async scanFile(fileName, config) {
|
|
125
171
|
this.lastConfig = config;
|
|
126
|
-
|
|
172
|
+
this.refreshChecker();
|
|
173
|
+
this.indexFile(fileName, config);
|
|
174
|
+
return this.extractFile(fileName, config);
|
|
127
175
|
}
|
|
128
176
|
async removeFile(fileName, config) {
|
|
129
177
|
const normalized = this.normalize(fileName);
|
|
178
|
+
const existing = this.compiler.getSourceFile(normalized);
|
|
179
|
+
if (existing) {
|
|
180
|
+
(0, symbolInvalidation_1.invalidateSourceFileSymbols)(existing, this.compiler.getChecker(), this.evaluationCache, this.resolvedCallCache, this.resolvedExportCache, this.returnPropagationCache, this.wrapperRegistry);
|
|
181
|
+
}
|
|
130
182
|
const affected = this.importGraph.collectDependents(normalized);
|
|
131
183
|
this.importGraph.removeFile(normalized);
|
|
132
184
|
this.compiler.removeFile(normalized);
|
|
185
|
+
this.fileIndices.delete(normalized);
|
|
133
186
|
this.fileResults.delete(normalized);
|
|
134
187
|
this.normalizedPathCache.delete(fileName);
|
|
135
188
|
this.normalizedPathCache.delete(normalized);
|
|
136
189
|
const nextConfig = config ?? this.lastConfig;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
await this.analyzeFile(file, nextConfig);
|
|
190
|
+
const toReanalyze = [...affected].filter((f) => f !== normalized);
|
|
191
|
+
if (toReanalyze.length > 0) {
|
|
192
|
+
await this.reanalyzeFiles(toReanalyze, nextConfig);
|
|
142
193
|
}
|
|
143
194
|
}
|
|
144
|
-
async analyzeFile(fileName, config) {
|
|
145
|
-
const normalized = this.normalize(fileName);
|
|
146
|
-
const source = this.compiler.getSourceFile(normalized);
|
|
147
|
-
if (!source) {
|
|
148
|
-
this.fileResults.delete(normalized);
|
|
149
|
-
return EMPTY_RESULT();
|
|
150
|
-
}
|
|
151
|
-
const result = await (0, recursiveWrapperAnalyzer_1.analyzeFileRecursive)(source, config, this.wrapperRegistry, this.evaluationCache, this.resolvedCallCache, this.returnPropagationCache, this.resolvedExportCache);
|
|
152
|
-
this.fileResults.set(normalized, result);
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
195
|
getAllEvents() {
|
|
156
196
|
const events = new Set();
|
|
157
197
|
for (const result of this.fileResults.values()) {
|
|
@@ -159,9 +199,21 @@ class EventraEngine {
|
|
|
159
199
|
events.add(event);
|
|
160
200
|
}
|
|
161
201
|
}
|
|
162
|
-
return [
|
|
163
|
-
|
|
164
|
-
|
|
202
|
+
return [...events].sort();
|
|
203
|
+
}
|
|
204
|
+
getAllFunctionWrappers() {
|
|
205
|
+
const wrappers = new Set();
|
|
206
|
+
for (const result of this.fileResults.values()) {
|
|
207
|
+
for (const name of result.detectedFunctionWrappers) {
|
|
208
|
+
if (name && name !== "anonymous" && !name.startsWith("__")) {
|
|
209
|
+
wrappers.add(name);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return [...wrappers].sort();
|
|
214
|
+
}
|
|
215
|
+
getScanResult(fileName) {
|
|
216
|
+
return this.fileResults.get(this.normalize(fileName)) ?? EMPTY_RESULT();
|
|
165
217
|
}
|
|
166
218
|
}
|
|
167
219
|
exports.EventraEngine = EventraEngine;
|
|
@@ -12,9 +12,8 @@ const processFile_1 = require("../filesystem/processFile");
|
|
|
12
12
|
const getVirtualFile_1 = require("../filesystem/getVirtualFile");
|
|
13
13
|
async function getParsedFile(file, cache) {
|
|
14
14
|
const existing = cache.get(file);
|
|
15
|
-
if (existing)
|
|
15
|
+
if (existing)
|
|
16
16
|
return existing;
|
|
17
|
-
}
|
|
18
17
|
const raw = await promises_1.default.readFile(file, "utf8");
|
|
19
18
|
const parsed = await (0, processFile_1.processFile)(file, raw);
|
|
20
19
|
cache.set(file, parsed);
|
|
@@ -28,13 +27,11 @@ async function scanProject(config) {
|
|
|
28
27
|
const engine = new EventraEngine_1.EventraEngine(process.cwd());
|
|
29
28
|
const cache = new Map();
|
|
30
29
|
const toScan = new Set();
|
|
31
|
-
// COLLECT
|
|
32
30
|
for (const file of files) {
|
|
33
31
|
try {
|
|
34
32
|
const raw = await promises_1.default.readFile(file, "utf8");
|
|
35
|
-
if (raw.length > 2_000_000)
|
|
33
|
+
if (raw.length > 2_000_000)
|
|
36
34
|
continue;
|
|
37
|
-
}
|
|
38
35
|
const parsed = await (0, processFile_1.processFile)(file, raw);
|
|
39
36
|
cache.set(file, parsed);
|
|
40
37
|
toScan.add(file);
|
|
@@ -42,29 +39,26 @@ async function scanProject(config) {
|
|
|
42
39
|
toScan.add(path_1.default.resolve(path_1.default.dirname(file), dep));
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
|
-
catch {
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error(`skip: ${file}`, err instanceof Error ? err.message : err);
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
|
-
|
|
46
|
+
const virtualFiles = [...toScan].map((f) => (0, getVirtualFile_1.getVirtualFile)(f));
|
|
49
47
|
engine.beginPreload();
|
|
50
48
|
for (const file of toScan) {
|
|
51
49
|
try {
|
|
52
50
|
const parsed = await getParsedFile(file, cache);
|
|
53
51
|
await engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
|
|
54
52
|
}
|
|
55
|
-
catch {
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`preload skip: ${file}`, err instanceof Error ? err.message : err);
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
engine.endPreload();
|
|
59
|
-
|
|
58
|
+
await engine.runFullAnalysis(virtualFiles, config);
|
|
60
59
|
const results = new Map();
|
|
61
60
|
for (const file of toScan) {
|
|
62
|
-
|
|
63
|
-
const result = await engine.scanFile((0, getVirtualFile_1.getVirtualFile)(file), config);
|
|
64
|
-
results.set(file, result);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
}
|
|
61
|
+
results.set(file, engine.getScanResult((0, getVirtualFile_1.getVirtualFile)(file)));
|
|
68
62
|
}
|
|
69
63
|
return {
|
|
70
64
|
engine,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildConfigFromScan = buildConfigFromScan;
|
|
4
|
+
exports.persistScanResults = persistScanResults;
|
|
5
|
+
const config_1 = require("../config/config");
|
|
6
|
+
function buildConfigFromScan(config, engine) {
|
|
7
|
+
const functionWrappers = engine
|
|
8
|
+
.getAllFunctionWrappers()
|
|
9
|
+
.map((name) => ({ name }));
|
|
10
|
+
return {
|
|
11
|
+
...config,
|
|
12
|
+
events: engine.getAllEvents(),
|
|
13
|
+
functionWrappers,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async function persistScanResults(config, engine) {
|
|
17
|
+
await (0, config_1.saveConfig)(buildConfigFromScan(config, engine));
|
|
18
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -33,13 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
return result;
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
36
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
40
|
const commander_1 = require("commander");
|
|
41
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
38
42
|
const program = new commander_1.Command();
|
|
39
43
|
program
|
|
40
44
|
.name("eventra")
|
|
41
45
|
.description("Eventra CLI")
|
|
42
|
-
.version(
|
|
46
|
+
.version(package_json_1.default.version);
|
|
43
47
|
program
|
|
44
48
|
.command("init")
|
|
45
49
|
.action(async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventra_dev/eventra-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Static analytics event extraction CLI powered by the TypeScript compiler API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsc -p tsconfig.json",
|
|
27
27
|
"dev": "tsx src/index.ts",
|
|
28
|
-
"test": "tsx tests/run.ts",
|
|
28
|
+
"test": "pnpm run clean && pnpm run build && tsx tests/run.ts",
|
|
29
29
|
"clean": "rm -rf dist"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
@@ -1,76 +0,0 @@
|
|
|
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.analyzeFileRecursive = analyzeFileRecursive;
|
|
7
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
-
const propagationAnalyzer_1 = require("../scanner/analyzer/propagationAnalyzer");
|
|
9
|
-
const extractor_1 = require("../extractor/extractor");
|
|
10
|
-
async function analyzeFileRecursive(sourceFile, config, wrapperRegistry, evaluationCache, resolvedCallCache, returnPropagationCache, resolvedExportCache) {
|
|
11
|
-
const checker = wrapperRegistry["checker"];
|
|
12
|
-
const sinks = [];
|
|
13
|
-
const wrappers = [];
|
|
14
|
-
const trackCalls = [];
|
|
15
|
-
// Support empty functionWrappers
|
|
16
|
-
const wrapperNames = new Set(config.functionWrappers?.map(w => w.name) ?? []);
|
|
17
|
-
function visit(node, parentFunction) {
|
|
18
|
-
// Detect calls to configured wrapper functions
|
|
19
|
-
if (typescript_1.default.isCallExpression(node)) {
|
|
20
|
-
const callName = getCallName(node.expression);
|
|
21
|
-
if (wrapperNames.has(callName)) {
|
|
22
|
-
// Track this wrapper call
|
|
23
|
-
sinks.push({ call: node, trackedArguments: [{ index: 0, propertyPath: [] }] });
|
|
24
|
-
trackCalls.push({
|
|
25
|
-
node,
|
|
26
|
-
sourceFile,
|
|
27
|
-
trackedArguments: node.arguments.filter(typescript_1.default.isExpression)
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// Detect and analyze function declarations / expressions / methods
|
|
32
|
-
if (typescript_1.default.isFunctionDeclaration(node) ||
|
|
33
|
-
typescript_1.default.isFunctionExpression(node) ||
|
|
34
|
-
typescript_1.default.isArrowFunction(node) ||
|
|
35
|
-
typescript_1.default.isMethodDeclaration(node)) {
|
|
36
|
-
const localSinks = sinks.filter(s => isNodeInside(s.call, node));
|
|
37
|
-
const wrapper = (0, propagationAnalyzer_1.analyzeWrapperPropagation)(node, checker, localSinks);
|
|
38
|
-
if (wrapper) {
|
|
39
|
-
wrappers.push(wrapper);
|
|
40
|
-
wrapperRegistry.set(wrapper);
|
|
41
|
-
}
|
|
42
|
-
// Recurse into function body
|
|
43
|
-
if (node.body) {
|
|
44
|
-
typescript_1.default.forEachChild(node.body, child => visit(child, node));
|
|
45
|
-
}
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// Recurse into child nodes
|
|
49
|
-
typescript_1.default.forEachChild(node, child => visit(child, parentFunction));
|
|
50
|
-
}
|
|
51
|
-
visit(sourceFile);
|
|
52
|
-
// Extract events from tracked calls and wrapper metadata
|
|
53
|
-
return (0, extractor_1.extractEvents)({
|
|
54
|
-
fileName: sourceFile.fileName,
|
|
55
|
-
sourceFile,
|
|
56
|
-
sinks,
|
|
57
|
-
trackCalls,
|
|
58
|
-
wrappers,
|
|
59
|
-
}, checker, config, evaluationCache, resolvedExportCache, resolvedCallCache, returnPropagationCache, wrapperRegistry);
|
|
60
|
-
}
|
|
61
|
-
function isNodeInside(child, fn) {
|
|
62
|
-
let current = child;
|
|
63
|
-
while (current) {
|
|
64
|
-
if (current === fn)
|
|
65
|
-
return true;
|
|
66
|
-
current = current.parent;
|
|
67
|
-
}
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
function getCallName(expr) {
|
|
71
|
-
if (typescript_1.default.isIdentifier(expr))
|
|
72
|
-
return expr.text;
|
|
73
|
-
if (typescript_1.default.isPropertyAccessExpression(expr) || typescript_1.default.isPropertyAccessChain(expr))
|
|
74
|
-
return expr.name.text;
|
|
75
|
-
return "";
|
|
76
|
-
}
|