@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/dist/engine/resolver.js
CHANGED
|
@@ -5,7 +5,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveNodeValue = resolveNodeValue;
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
-
function resolveNodeValue(node, checker) {
|
|
8
|
+
function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
|
|
9
|
+
if (seen.has(node))
|
|
10
|
+
return null;
|
|
11
|
+
seen.add(node);
|
|
12
|
+
// param mapping
|
|
13
|
+
if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
14
|
+
return resolveNodeValue(paramMap.get(node.text), checker, paramMap);
|
|
15
|
+
}
|
|
9
16
|
if (typescript_1.default.isStringLiteral(node)) {
|
|
10
17
|
return { values: [node.text], dynamic: false };
|
|
11
18
|
}
|
|
@@ -18,8 +25,8 @@ function resolveNodeValue(node, checker) {
|
|
|
18
25
|
return { values: [result], dynamic: true };
|
|
19
26
|
}
|
|
20
27
|
if (typescript_1.default.isConditionalExpression(node)) {
|
|
21
|
-
const a = resolveNodeValue(node.whenTrue, checker);
|
|
22
|
-
const b = resolveNodeValue(node.whenFalse, checker);
|
|
28
|
+
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
29
|
+
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
23
30
|
return {
|
|
24
31
|
values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
|
|
25
32
|
dynamic: true,
|
|
@@ -34,7 +41,7 @@ function resolveNodeValue(node, checker) {
|
|
|
34
41
|
}
|
|
35
42
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
36
43
|
if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
|
|
37
|
-
return resolveNodeValue(decl.initializer, checker);
|
|
44
|
+
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
38
45
|
}
|
|
39
46
|
if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
|
|
40
47
|
if (typescript_1.default.isStringLiteral(decl.initializer)) {
|
|
@@ -65,5 +72,31 @@ function resolveNodeValue(node, checker) {
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
}
|
|
75
|
+
if (typescript_1.default.isObjectLiteralExpression(node)) {
|
|
76
|
+
const values = [];
|
|
77
|
+
for (const prop of node.properties) {
|
|
78
|
+
if (!typescript_1.default.isPropertyAssignment(prop))
|
|
79
|
+
continue;
|
|
80
|
+
const name = prop.name.getText();
|
|
81
|
+
if (name === "event") {
|
|
82
|
+
const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
|
|
83
|
+
if (res)
|
|
84
|
+
values.push(...res.values);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { values, dynamic: true };
|
|
88
|
+
}
|
|
89
|
+
if (typescript_1.default.isBinaryExpression(node)) {
|
|
90
|
+
if (node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
|
|
91
|
+
const left = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
92
|
+
const right = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
93
|
+
if (left && right) {
|
|
94
|
+
return {
|
|
95
|
+
values: left.values.flatMap(l => right.values.map(r => l + r)),
|
|
96
|
+
dynamic: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
68
101
|
return null;
|
|
69
102
|
}
|
package/dist/engine/scanner.js
CHANGED
|
@@ -7,7 +7,37 @@ exports.scanSource = scanSource;
|
|
|
7
7
|
const typescript_1 = __importDefault(require("typescript"));
|
|
8
8
|
const resolver_1 = require("./resolver");
|
|
9
9
|
const callResolver_1 = require("./callResolver");
|
|
10
|
-
|
|
10
|
+
const functionAnalyzer_1 = require("./functionAnalyzer");
|
|
11
|
+
function getCallName(expr) {
|
|
12
|
+
if (typescript_1.default.isIdentifier(expr))
|
|
13
|
+
return expr.text;
|
|
14
|
+
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
15
|
+
return expr.name.text;
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
function pickBestArg(args, checker, paramMap) {
|
|
20
|
+
let best = null;
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
|
|
23
|
+
if (!res?.values?.length)
|
|
24
|
+
continue;
|
|
25
|
+
let score = 0;
|
|
26
|
+
if (!res.dynamic)
|
|
27
|
+
score += 3;
|
|
28
|
+
if (res.values.every(v => v.length < 50))
|
|
29
|
+
score += 2;
|
|
30
|
+
if (res.values.some(v => v.includes("*")))
|
|
31
|
+
score -= 2;
|
|
32
|
+
if (!best || score > best.score) {
|
|
33
|
+
best = { score, node: arg };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return best?.node;
|
|
37
|
+
}
|
|
38
|
+
function scanSource(source, checker, visited = new Set(), depth = 0) {
|
|
39
|
+
if (depth > 5)
|
|
40
|
+
return new Set();
|
|
11
41
|
const events = new Set();
|
|
12
42
|
if (visited.has(source.fileName))
|
|
13
43
|
return events;
|
|
@@ -16,19 +46,47 @@ function scanSource(source, checker, visited = new Set()) {
|
|
|
16
46
|
if (typescript_1.default.isCallExpression(node)) {
|
|
17
47
|
const name = getCallName(node.expression);
|
|
18
48
|
if (/(track|event|capture|send)/i.test(name)) {
|
|
19
|
-
let target = node.arguments[0];
|
|
20
49
|
const fn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
|
|
21
|
-
|
|
22
|
-
if (fn) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
50
|
+
const paramMap = new Map();
|
|
51
|
+
if (fn && typescript_1.default.isFunctionLike(fn)) {
|
|
52
|
+
fn.parameters.forEach((param, index) => {
|
|
53
|
+
const arg = node.arguments[index];
|
|
54
|
+
if (!arg)
|
|
55
|
+
return;
|
|
56
|
+
if (typescript_1.default.isIdentifier(param.name)) {
|
|
57
|
+
paramMap.set(param.name.text, arg);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (fn && typescript_1.default.isFunctionLike(fn)) {
|
|
62
|
+
const idx = (0, functionAnalyzer_1.findTrackParamIndex)(fn);
|
|
63
|
+
if (idx !== null) {
|
|
64
|
+
const realArg = node.arguments[idx];
|
|
65
|
+
if (realArg) {
|
|
66
|
+
const res = (0, resolver_1.resolveNodeValue)(realArg, checker, paramMap);
|
|
67
|
+
res?.values.forEach(v => {
|
|
68
|
+
if (v &&
|
|
69
|
+
v.length > 1 &&
|
|
70
|
+
v.length < 100 &&
|
|
71
|
+
!v.includes(" ") &&
|
|
72
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)) {
|
|
73
|
+
events.add(v);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
26
78
|
}
|
|
27
79
|
}
|
|
80
|
+
// fallback (heuristic)
|
|
81
|
+
const target = pickBestArg(node.arguments, checker, paramMap);
|
|
28
82
|
if (target) {
|
|
29
|
-
const res = (0, resolver_1.resolveNodeValue)(target, checker);
|
|
83
|
+
const res = (0, resolver_1.resolveNodeValue)(target, checker, paramMap);
|
|
30
84
|
res?.values.forEach(v => {
|
|
31
|
-
if (v &&
|
|
85
|
+
if (v &&
|
|
86
|
+
v.length > 1 &&
|
|
87
|
+
v.length < 100 &&
|
|
88
|
+
!v.includes(" ") &&
|
|
89
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)) {
|
|
32
90
|
events.add(v);
|
|
33
91
|
}
|
|
34
92
|
});
|
|
@@ -40,11 +98,3 @@ function scanSource(source, checker, visited = new Set()) {
|
|
|
40
98
|
visit(source);
|
|
41
99
|
return events;
|
|
42
100
|
}
|
|
43
|
-
function getCallName(expr) {
|
|
44
|
-
if (typescript_1.default.isIdentifier(expr))
|
|
45
|
-
return expr.text;
|
|
46
|
-
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
47
|
-
return expr.name.text;
|
|
48
|
-
}
|
|
49
|
-
return "";
|
|
50
|
-
}
|
package/dist/utils/config.js
CHANGED
|
@@ -36,8 +36,13 @@ async function loadConfig() {
|
|
|
36
36
|
if (!(await fs_extra_1.default.pathExists(configPath))) {
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
try {
|
|
40
|
+
const config = await fs_extra_1.default.readJSON(configPath);
|
|
41
|
+
return normalizeConfig(config);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
41
46
|
}
|
|
42
47
|
async function saveConfig(config) {
|
|
43
48
|
const configPath = path_1.default.join(process.cwd(), exports.CONFIG_NAME);
|
|
@@ -2,5 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseAstro = parseAstro;
|
|
4
4
|
function parseAstro(content) {
|
|
5
|
-
|
|
5
|
+
const frontmatter = content.match(/---([\s\S]*?)---/);
|
|
6
|
+
const scripts = [
|
|
7
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
8
|
+
];
|
|
9
|
+
return [
|
|
10
|
+
frontmatter?.[1] ?? "",
|
|
11
|
+
...scripts.map(s => s[1])
|
|
12
|
+
].join("\n");
|
|
6
13
|
}
|
|
@@ -2,5 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseSvelte = parseSvelte;
|
|
4
4
|
function parseSvelte(content) {
|
|
5
|
-
|
|
5
|
+
const scriptMatches = [
|
|
6
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
7
|
+
];
|
|
8
|
+
return scriptMatches.map(m => m[1]).join("\n");
|
|
6
9
|
}
|
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseVue = parseVue;
|
|
4
4
|
function parseVue(content) {
|
|
5
|
-
const
|
|
5
|
+
const scriptMatches = [
|
|
6
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
7
|
+
];
|
|
8
|
+
const script = scriptMatches.map(m => m[1]).join("\n");
|
|
9
|
+
// template
|
|
6
10
|
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
|
|
7
|
-
const script = scriptMatch?.[1] ?? "";
|
|
8
11
|
let template = templateMatch?.[1] ?? "";
|
|
9
12
|
template = template.replace(/<!--[\s\S]*?-->/g, "");
|
|
13
|
+
// Vue → JSX
|
|
10
14
|
template = template
|
|
15
|
+
.replace(/\bclass=/g, "className=")
|
|
16
|
+
.replace(/\bfor=/g, "htmlFor=")
|
|
17
|
+
.replace(/@(\w+)=/g, (_, e) => {
|
|
18
|
+
const name = e.charAt(0).toUpperCase() + e.slice(1);
|
|
19
|
+
return `on${name}=`;
|
|
20
|
+
})
|
|
11
21
|
.replace(/v-if="[^"]+"/g, "")
|
|
12
|
-
.replace(/v-for="[^"]+"/g, "")
|
|
13
|
-
.replace(/@click=/g, "onClick=");
|
|
22
|
+
.replace(/v-for="[^"]+"/g, "");
|
|
14
23
|
const jsx = `
|
|
15
24
|
const __VUE_JSX__ = (
|
|
16
25
|
<>
|
|
@@ -13,7 +13,9 @@ function processFile(file, content) {
|
|
|
13
13
|
content = (0, svelte_1.parseSvelte)(content);
|
|
14
14
|
if (parser === "astro")
|
|
15
15
|
content = (0, astro_1.parseAstro)(content);
|
|
16
|
-
const virtualFile = file.endsWith(".vue")
|
|
16
|
+
const virtualFile = file.endsWith(".vue") ||
|
|
17
|
+
file.endsWith(".svelte") ||
|
|
18
|
+
file.endsWith(".astro")
|
|
17
19
|
? file + ".tsx"
|
|
18
20
|
: file;
|
|
19
21
|
return { content, virtualFile };
|
package/package.json
CHANGED
package/src/commands/check.ts
CHANGED
|
@@ -30,8 +30,12 @@ export async function check({ fix = false }: { fix?: boolean }) {
|
|
|
30
30
|
|
|
31
31
|
engine.scanFile(virtualFile, content);
|
|
32
32
|
|
|
33
|
-
} catch {
|
|
34
|
-
console.log(chalk.
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.log(chalk.yellow(`Skipped: ${file}`));
|
|
35
|
+
|
|
36
|
+
if (err instanceof Error) {
|
|
37
|
+
console.log(chalk.gray(err.message));
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -39,14 +43,17 @@ export async function check({ fix = false }: { fix?: boolean }) {
|
|
|
39
43
|
const known = new Set(config.events ?? []);
|
|
40
44
|
|
|
41
45
|
const newEvents = [...found].filter(e => !known.has(e));
|
|
46
|
+
const removed = [...known].filter(e => !found.has(e));
|
|
42
47
|
|
|
43
48
|
if (fix) {
|
|
49
|
+
const merged = new Set(config.events ?? []);
|
|
50
|
+
|
|
44
51
|
newEvents.forEach(e => {
|
|
45
|
-
|
|
52
|
+
merged.add(e);
|
|
46
53
|
console.log(chalk.green(`+ ${e}`));
|
|
47
54
|
});
|
|
48
55
|
|
|
49
|
-
config.events = [...
|
|
56
|
+
config.events = [...merged].sort();
|
|
50
57
|
|
|
51
58
|
await saveConfig(config);
|
|
52
59
|
|
|
@@ -54,9 +61,16 @@ export async function check({ fix = false }: { fix?: boolean }) {
|
|
|
54
61
|
return;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
if (newEvents.length) {
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
if (newEvents.length || removed.length) {
|
|
65
|
+
if (newEvents.length) {
|
|
66
|
+
console.log(chalk.red("\nNew events:"));
|
|
67
|
+
newEvents.forEach(e => console.log(chalk.red(`+ ${e}`)));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (removed.length) {
|
|
71
|
+
console.log(chalk.yellow("\nRemoved events:"));
|
|
72
|
+
removed.forEach(e => console.log(chalk.yellow(`- ${e}`)));
|
|
73
|
+
}
|
|
60
74
|
|
|
61
75
|
process.exit(1);
|
|
62
76
|
}
|
package/src/commands/send.ts
CHANGED
|
@@ -81,6 +81,9 @@ export async function send() {
|
|
|
81
81
|
)
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
86
|
+
|
|
84
87
|
try {
|
|
85
88
|
const res = await fetch(
|
|
86
89
|
endpoint,
|
|
@@ -99,8 +102,9 @@ export async function send() {
|
|
|
99
102
|
version: CLI_VERSION,
|
|
100
103
|
runtime: "node"
|
|
101
104
|
}
|
|
102
|
-
})
|
|
103
|
-
|
|
105
|
+
}),
|
|
106
|
+
signal: controller.signal
|
|
107
|
+
},
|
|
104
108
|
);
|
|
105
109
|
|
|
106
110
|
if (!res.ok) {
|
|
@@ -203,5 +207,7 @@ export async function send() {
|
|
|
203
207
|
)
|
|
204
208
|
);
|
|
205
209
|
}
|
|
210
|
+
} finally {
|
|
211
|
+
clearTimeout(timeout);
|
|
206
212
|
}
|
|
207
213
|
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -31,9 +31,12 @@ export async function sync() {
|
|
|
31
31
|
|
|
32
32
|
engine.scanFile(virtualFile, content);
|
|
33
33
|
|
|
34
|
-
} catch (
|
|
35
|
-
console.log(chalk.
|
|
36
|
-
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.log(chalk.yellow(`Skipped: ${file}`));
|
|
36
|
+
|
|
37
|
+
if (err instanceof Error) {
|
|
38
|
+
console.log(chalk.gray(err.message));
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
package/src/commands/watch.ts
CHANGED
|
@@ -5,31 +5,8 @@ import fg from "fast-glob";
|
|
|
5
5
|
|
|
6
6
|
import { loadConfig, saveConfig } from "../utils/config";
|
|
7
7
|
import { EventraEngine } from "../engine/engine";
|
|
8
|
+
import { processFile } from "../utils/processFile";
|
|
8
9
|
|
|
9
|
-
import { detectParser } from "../utils/parsers/router";
|
|
10
|
-
import { parseVue } from "../utils/parsers/vue";
|
|
11
|
-
import { parseSvelte } from "../utils/parsers/svelte";
|
|
12
|
-
import { parseAstro } from "../utils/parsers/astro";
|
|
13
|
-
|
|
14
|
-
// PREPROCESS (CONTENT)
|
|
15
|
-
function preprocess(file: string, content: string) {
|
|
16
|
-
const parser = detectParser(file);
|
|
17
|
-
|
|
18
|
-
if (parser === "vue") return parseVue(content);
|
|
19
|
-
if (parser === "svelte") return parseSvelte(content);
|
|
20
|
-
if (parser === "astro") return parseAstro(content);
|
|
21
|
-
|
|
22
|
-
return content;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// VIRTUAL FILE (PATH)
|
|
26
|
-
function toVirtualFile(file: string) {
|
|
27
|
-
return file.endsWith(".vue")
|
|
28
|
-
? file + ".tsx"
|
|
29
|
-
: file;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// WATCH
|
|
33
10
|
export async function watch() {
|
|
34
11
|
const config = await loadConfig();
|
|
35
12
|
if (!config) return;
|
|
@@ -40,20 +17,13 @@ export async function watch() {
|
|
|
40
17
|
ignore: config.sync.exclude,
|
|
41
18
|
});
|
|
42
19
|
|
|
43
|
-
if (!files.length) {
|
|
44
|
-
console.log(chalk.yellow("No files found"));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
20
|
const engine = new EventraEngine(process.cwd());
|
|
49
21
|
|
|
50
|
-
// INITIAL SCAN
|
|
51
22
|
for (const file of files) {
|
|
52
23
|
try {
|
|
53
24
|
const raw = await fs.readFile(file, "utf-8");
|
|
54
25
|
|
|
55
|
-
const content =
|
|
56
|
-
const virtualFile = toVirtualFile(file);
|
|
26
|
+
const { content, virtualFile } = processFile(file, raw);
|
|
57
27
|
|
|
58
28
|
engine.scanFile(virtualFile, content);
|
|
59
29
|
|
|
@@ -69,9 +39,9 @@ export async function watch() {
|
|
|
69
39
|
chalk.gray(`Initial: ${config.events.length} events\n`)
|
|
70
40
|
);
|
|
71
41
|
|
|
72
|
-
// STATE
|
|
73
42
|
let locked = false;
|
|
74
43
|
let queued = new Set<string>();
|
|
44
|
+
let timer: NodeJS.Timeout | null = null;
|
|
75
45
|
|
|
76
46
|
const run = async () => {
|
|
77
47
|
if (locked) return;
|
|
@@ -84,8 +54,7 @@ export async function watch() {
|
|
|
84
54
|
try {
|
|
85
55
|
const raw = await fs.readFile(file, "utf-8");
|
|
86
56
|
|
|
87
|
-
const content =
|
|
88
|
-
const virtualFile = toVirtualFile(file);
|
|
57
|
+
const { content, virtualFile } = processFile(file, raw);
|
|
89
58
|
|
|
90
59
|
engine.updateFile(virtualFile, content);
|
|
91
60
|
|
|
@@ -116,48 +85,33 @@ export async function watch() {
|
|
|
116
85
|
|
|
117
86
|
const schedule = (file: string) => {
|
|
118
87
|
queued.add(file);
|
|
119
|
-
|
|
88
|
+
|
|
89
|
+
if (timer) clearTimeout(timer);
|
|
90
|
+
|
|
91
|
+
timer = setTimeout(run, 100);
|
|
120
92
|
};
|
|
121
93
|
|
|
122
|
-
// WATCHER
|
|
123
94
|
const watcher = chokidar.watch(config.sync.include, {
|
|
124
95
|
ignored: config.sync.exclude,
|
|
125
96
|
ignoreInitial: true,
|
|
126
|
-
awaitWriteFinish: {
|
|
127
|
-
stabilityThreshold: 50,
|
|
128
|
-
pollInterval: 10,
|
|
129
|
-
},
|
|
130
97
|
});
|
|
131
98
|
|
|
132
99
|
watcher
|
|
133
|
-
.on("add",
|
|
134
|
-
|
|
135
|
-
schedule(file);
|
|
136
|
-
})
|
|
137
|
-
.on("change", (file) => {
|
|
138
|
-
console.log(chalk.gray(`change: ${file}`));
|
|
139
|
-
schedule(file);
|
|
140
|
-
})
|
|
100
|
+
.on("add", schedule)
|
|
101
|
+
.on("change", schedule)
|
|
141
102
|
.on("unlink", async (file) => {
|
|
142
103
|
console.log(chalk.gray(`remove: ${file}`));
|
|
143
104
|
|
|
144
|
-
const virtualFile =
|
|
105
|
+
const { virtualFile } = processFile(file, "");
|
|
145
106
|
|
|
146
|
-
engine.removeFile
|
|
107
|
+
engine.removeFile(virtualFile);
|
|
147
108
|
|
|
148
109
|
const next = engine.getAllEvents();
|
|
149
110
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (removed.length) {
|
|
153
|
-
removed.forEach(e => console.log(chalk.red(`- ${e}`)));
|
|
154
|
-
|
|
155
|
-
config.events = next.sort();
|
|
156
|
-
await saveConfig(config);
|
|
157
|
-
}
|
|
111
|
+
config.events = next.sort();
|
|
112
|
+
await saveConfig(config);
|
|
158
113
|
});
|
|
159
114
|
|
|
160
|
-
// EXIT
|
|
161
115
|
process.on("SIGINT", async () => {
|
|
162
116
|
console.log(chalk.yellow("\nStopping watcher..."));
|
|
163
117
|
await watcher.close();
|
|
@@ -5,38 +5,21 @@ export function resolveFunctionFromCall(
|
|
|
5
5
|
checker: ts.TypeChecker
|
|
6
6
|
): ts.FunctionLikeDeclaration | null {
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const type = checker.getTypeAtLocation(expr);
|
|
9
|
+
const signatures = type.getCallSignatures();
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (!symbol) return null;
|
|
15
|
-
|
|
16
|
-
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
17
|
-
symbol = checker.getAliasedSymbol(symbol);
|
|
18
|
-
}
|
|
11
|
+
for (const sig of signatures) {
|
|
12
|
+
const decl = sig.getDeclaration();
|
|
19
13
|
|
|
20
|
-
for (const decl of symbol.getDeclarations() ?? []) {
|
|
21
14
|
if (
|
|
22
|
-
|
|
23
|
-
ts.
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
decl &&
|
|
16
|
+
(ts.isFunctionDeclaration(decl) ||
|
|
17
|
+
ts.isMethodDeclaration(decl) ||
|
|
18
|
+
ts.isFunctionExpression(decl) ||
|
|
19
|
+
ts.isArrowFunction(decl))
|
|
26
20
|
) {
|
|
27
21
|
return decl;
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
31
|
-
const init = decl.initializer;
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
ts.isArrowFunction(init) ||
|
|
35
|
-
ts.isFunctionExpression(init)
|
|
36
|
-
) {
|
|
37
|
-
return init;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
23
|
}
|
|
41
24
|
|
|
42
25
|
return null;
|
package/src/engine/engine.ts
CHANGED
|
@@ -22,7 +22,6 @@ export class EventraEngine {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
this.fileHash.set(file, h);
|
|
25
|
-
|
|
26
25
|
this.ts.updateFile(file, content);
|
|
27
26
|
|
|
28
27
|
const source = this.ts.getSourceFile(file);
|
|
@@ -31,7 +30,6 @@ export class EventraEngine {
|
|
|
31
30
|
if (!source || !checker) return new Set();
|
|
32
31
|
|
|
33
32
|
this.graph.update(file, source);
|
|
34
|
-
|
|
35
33
|
const events = scanSource(source, checker);
|
|
36
34
|
|
|
37
35
|
this.fileEvents.set(file, events);
|
|
@@ -40,7 +38,20 @@ export class EventraEngine {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
updateFile(file: string, content: string) {
|
|
43
|
-
|
|
41
|
+
const events = this.scanFile(file, content);
|
|
42
|
+
const dependents = this.graph.getDependents(file);
|
|
43
|
+
|
|
44
|
+
for (const dep of dependents) {
|
|
45
|
+
const source = this.ts.getSourceFile(dep);
|
|
46
|
+
const checker = this.ts.getChecker();
|
|
47
|
+
|
|
48
|
+
if (!source || !checker) continue;
|
|
49
|
+
|
|
50
|
+
const ev = scanSource(source, checker);
|
|
51
|
+
this.fileEvents.set(dep, ev);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return events;
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
getAllEvents() {
|
|
@@ -56,5 +67,6 @@ export class EventraEngine {
|
|
|
56
67
|
removeFile(file: string) {
|
|
57
68
|
this.fileEvents.delete(file);
|
|
58
69
|
this.fileHash.delete(file);
|
|
70
|
+
this.graph.remove(file);
|
|
59
71
|
}
|
|
60
72
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 visit(node: ts.Node) {
|
|
17
|
+
if (ts.isCallExpression(node)) {
|
|
18
|
+
const name = getCallName(node.expression);
|
|
19
|
+
|
|
20
|
+
if (/(track|event|capture|send)/i.test(name)) {
|
|
21
|
+
for (const arg of node.arguments) {
|
|
22
|
+
if (ts.isIdentifier(arg)) {
|
|
23
|
+
const idx = paramIndex.get(arg.text);
|
|
24
|
+
if (idx !== undefined) {
|
|
25
|
+
result = idx;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ts.forEachChild(node, visit);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (fn.body) {
|
|
37
|
+
visit(fn.body);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getCallName(expr: ts.Expression): string {
|
|
44
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
45
|
+
|
|
46
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
47
|
+
return expr.name.text;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return "";
|
|
51
|
+
}
|