@eventra_dev/eventra-cli 0.1.2 → 0.1.3
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/graph.js +43 -5
- package/dist/engine/languageService.js +25 -18
- package/dist/engine/resolver.js +37 -4
- package/dist/engine/scanner.js +25 -7
- 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/graph.ts +52 -5
- package/src/engine/languageService.ts +50 -20
- package/src/engine/resolver.ts +51 -4
- package/src/engine/scanner.ts +30 -8
- 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/src/engine/resolver.ts
CHANGED
|
@@ -7,9 +7,23 @@ export type ResolveResult = {
|
|
|
7
7
|
|
|
8
8
|
export function resolveNodeValue(
|
|
9
9
|
node: ts.Node,
|
|
10
|
-
checker: ts.TypeChecker
|
|
10
|
+
checker: ts.TypeChecker,
|
|
11
|
+
paramMap?: Map<string, ts.Expression>,
|
|
12
|
+
seen = new Set<ts.Node>()
|
|
11
13
|
): ResolveResult | null {
|
|
12
14
|
|
|
15
|
+
if (seen.has(node)) return null;
|
|
16
|
+
seen.add(node);
|
|
17
|
+
|
|
18
|
+
// param mapping
|
|
19
|
+
if (ts.isIdentifier(node) && paramMap?.has(node.text)) {
|
|
20
|
+
return resolveNodeValue(
|
|
21
|
+
paramMap.get(node.text)!,
|
|
22
|
+
checker,
|
|
23
|
+
paramMap
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
if (ts.isStringLiteral(node)) {
|
|
14
28
|
return { values: [node.text], dynamic: false };
|
|
15
29
|
}
|
|
@@ -26,8 +40,8 @@ export function resolveNodeValue(
|
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
if (ts.isConditionalExpression(node)) {
|
|
29
|
-
const a = resolveNodeValue(node.whenTrue, checker);
|
|
30
|
-
const b = resolveNodeValue(node.whenFalse, checker);
|
|
43
|
+
const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
|
|
44
|
+
const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
|
|
31
45
|
|
|
32
46
|
return {
|
|
33
47
|
values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
|
|
@@ -45,7 +59,7 @@ export function resolveNodeValue(
|
|
|
45
59
|
|
|
46
60
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
47
61
|
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
48
|
-
return resolveNodeValue(decl.initializer, checker);
|
|
62
|
+
return resolveNodeValue(decl.initializer, checker, paramMap, seen);
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
if (ts.isEnumMember(decl) && decl.initializer) {
|
|
@@ -82,5 +96,38 @@ export function resolveNodeValue(
|
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
|
|
99
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
100
|
+
const values: string[] = [];
|
|
101
|
+
|
|
102
|
+
for (const prop of node.properties) {
|
|
103
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
104
|
+
|
|
105
|
+
const name = prop.name.getText();
|
|
106
|
+
|
|
107
|
+
if (name === "event") {
|
|
108
|
+
const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
|
|
109
|
+
if (res) values.push(...res.values);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { values, dynamic: true };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (ts.isBinaryExpression(node)) {
|
|
117
|
+
if (node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
118
|
+
const left = resolveNodeValue(node.left, checker, paramMap, seen);
|
|
119
|
+
const right = resolveNodeValue(node.right, checker, paramMap, seen);
|
|
120
|
+
|
|
121
|
+
if (left && right) {
|
|
122
|
+
return {
|
|
123
|
+
values: left.values.flatMap(l =>
|
|
124
|
+
right.values.map(r => l + r)
|
|
125
|
+
),
|
|
126
|
+
dynamic: true,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
85
132
|
return null;
|
|
86
133
|
}
|
package/src/engine/scanner.ts
CHANGED
|
@@ -5,8 +5,10 @@ import { resolveFunctionFromCall } from "./callResolver";
|
|
|
5
5
|
export function scanSource(
|
|
6
6
|
source: ts.SourceFile,
|
|
7
7
|
checker: ts.TypeChecker,
|
|
8
|
-
visited = new Set<string>()
|
|
9
|
-
|
|
8
|
+
visited = new Set<string>(),
|
|
9
|
+
depth = 0
|
|
10
|
+
): Set<string> {
|
|
11
|
+
if (depth > 5) return new Set();
|
|
10
12
|
const events = new Set<string>();
|
|
11
13
|
|
|
12
14
|
if (visited.has(source.fileName)) return events;
|
|
@@ -17,24 +19,44 @@ export function scanSource(
|
|
|
17
19
|
const name = getCallName(node.expression);
|
|
18
20
|
|
|
19
21
|
if (/(track|event|capture|send)/i.test(name)) {
|
|
20
|
-
let target = node.arguments[0];
|
|
21
22
|
|
|
22
23
|
const fn = resolveFunctionFromCall(node.expression, checker);
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
+
// param mapping
|
|
26
|
+
let paramMap = new Map<string, ts.Expression>();
|
|
27
|
+
|
|
28
|
+
if (fn && ts.isFunctionLike(fn)) {
|
|
29
|
+
fn.parameters.forEach((param, index) => {
|
|
30
|
+
const arg = node.arguments[index];
|
|
31
|
+
if (!arg) return;
|
|
32
|
+
|
|
33
|
+
if (ts.isIdentifier(param.name)) {
|
|
34
|
+
paramMap.set(param.name.text, arg);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// cross-file traversal
|
|
25
40
|
if (fn) {
|
|
26
41
|
const sf = fn.getSourceFile();
|
|
27
42
|
|
|
28
43
|
if (!visited.has(sf.fileName)) {
|
|
29
|
-
scanSource(sf, checker, visited
|
|
44
|
+
scanSource(sf, checker, visited, depth + 1)
|
|
45
|
+
.forEach(e => events.add(e));
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
const res = resolveNodeValue(
|
|
49
|
+
for (const arg of node.arguments) {
|
|
50
|
+
const res = resolveNodeValue(arg, checker, paramMap);
|
|
35
51
|
|
|
36
52
|
res?.values.forEach(v => {
|
|
37
|
-
if (
|
|
53
|
+
if (
|
|
54
|
+
v &&
|
|
55
|
+
v.length > 1 &&
|
|
56
|
+
v.length < 100 &&
|
|
57
|
+
!v.includes(" ") &&
|
|
58
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)
|
|
59
|
+
) {
|
|
38
60
|
events.add(v);
|
|
39
61
|
}
|
|
40
62
|
});
|
package/src/utils/config.ts
CHANGED
|
@@ -12,8 +12,7 @@ export function normalizeConfig(
|
|
|
12
12
|
events: config.events ?? [],
|
|
13
13
|
endpoint: config.endpoint ?? "",
|
|
14
14
|
wrappers: config.wrappers ?? [],
|
|
15
|
-
functionWrappers:
|
|
16
|
-
config.functionWrappers ?? [],
|
|
15
|
+
functionWrappers: config.functionWrappers ?? [],
|
|
17
16
|
aliases: config.aliases ?? {},
|
|
18
17
|
sync: config.sync ?? {
|
|
19
18
|
include: [
|
|
@@ -30,43 +29,26 @@ export function normalizeConfig(
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export async function loadConfig(): Promise<EventraConfig | null> {
|
|
33
|
-
const configPath = path.join(
|
|
34
|
-
process.cwd(),
|
|
35
|
-
CONFIG_NAME
|
|
36
|
-
);
|
|
32
|
+
const configPath = path.join(process.cwd(), CONFIG_NAME);
|
|
37
33
|
|
|
38
|
-
if (
|
|
39
|
-
!(await fs.pathExists(
|
|
40
|
-
configPath
|
|
41
|
-
))
|
|
42
|
-
) {
|
|
34
|
+
if (!(await fs.pathExists(configPath))) {
|
|
43
35
|
return null;
|
|
44
36
|
}
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
await fs.readJSON(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
try {
|
|
39
|
+
const config = await fs.readJSON(configPath);
|
|
40
|
+
return normalizeConfig(config);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
export async function saveConfig(
|
|
55
|
-
|
|
56
|
-
) {
|
|
57
|
-
const configPath = path.join(
|
|
58
|
-
process.cwd(),
|
|
59
|
-
CONFIG_NAME
|
|
60
|
-
);
|
|
46
|
+
export async function saveConfig(config: EventraConfig) {
|
|
47
|
+
const configPath = path.join(process.cwd(), CONFIG_NAME);
|
|
61
48
|
|
|
62
|
-
const normalized =
|
|
63
|
-
normalizeConfig(config);
|
|
49
|
+
const normalized = normalizeConfig(config);
|
|
64
50
|
|
|
65
|
-
await fs.writeJSON(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
spaces: 2
|
|
70
|
-
}
|
|
71
|
-
);
|
|
51
|
+
await fs.writeJSON(configPath, normalized, {
|
|
52
|
+
spaces: 2
|
|
53
|
+
});
|
|
72
54
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
export function parseAstro(
|
|
2
|
-
content
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export function parseAstro(content: string) {
|
|
2
|
+
const frontmatter = content.match(/---([\s\S]*?)---/);
|
|
3
|
+
|
|
4
|
+
const scripts = [
|
|
5
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
return [
|
|
9
|
+
frontmatter?.[1] ?? "",
|
|
10
|
+
...scripts.map(s => s[1])
|
|
11
|
+
].join("\n");
|
|
5
12
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
export function detectParser(
|
|
2
|
-
file
|
|
3
|
-
) {
|
|
4
|
-
if (file.endsWith(".vue"))
|
|
5
|
-
return "vue";
|
|
1
|
+
export function detectParser(file: string) {
|
|
2
|
+
file = file.toLowerCase();
|
|
6
3
|
|
|
7
|
-
if (file.endsWith(".
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (file.endsWith(".astro"))
|
|
11
|
-
return "astro";
|
|
4
|
+
if (file.endsWith(".vue")) return "vue";
|
|
5
|
+
if (file.endsWith(".svelte")) return "svelte";
|
|
6
|
+
if (file.endsWith(".astro")) return "astro";
|
|
12
7
|
|
|
13
8
|
return "ts";
|
|
14
9
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export function parseSvelte(
|
|
2
|
-
|
|
3
|
-
)
|
|
4
|
-
|
|
1
|
+
export function parseSvelte(content: string) {
|
|
2
|
+
const scriptMatches = [
|
|
3
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
4
|
+
];
|
|
5
|
+
|
|
6
|
+
return scriptMatches.map(m => m[1]).join("\n");
|
|
5
7
|
}
|
package/src/utils/parsers/vue.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
export function parseVue(content: string) {
|
|
2
|
-
const
|
|
3
|
-
|
|
2
|
+
const scriptMatches = [
|
|
3
|
+
...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
|
|
4
|
+
];
|
|
5
|
+
|
|
6
|
+
const script = scriptMatches.map(m => m[1]).join("\n");
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
// template
|
|
9
|
+
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
|
|
6
10
|
let template = templateMatch?.[1] ?? "";
|
|
7
11
|
|
|
8
12
|
template = template.replace(/<!--[\s\S]*?-->/g, "");
|
|
9
13
|
|
|
14
|
+
// Vue → JSX
|
|
10
15
|
template = template
|
|
16
|
+
.replace(/\bclass=/g, "className=")
|
|
17
|
+
.replace(/\bfor=/g, "htmlFor=")
|
|
18
|
+
.replace(/@(\w+)=/g, (_, e) => {
|
|
19
|
+
const name = e.charAt(0).toUpperCase() + e.slice(1);
|
|
20
|
+
return `on${name}=`;
|
|
21
|
+
})
|
|
11
22
|
.replace(/v-if="[^"]+"/g, "")
|
|
12
|
-
.replace(/v-for="[^"]+"/g, "")
|
|
13
|
-
.replace(/@click=/g, "onClick=");
|
|
23
|
+
.replace(/v-for="[^"]+"/g, "");
|
|
14
24
|
|
|
15
25
|
const jsx = `
|
|
16
26
|
const __VUE_JSX__ = (
|
package/src/utils/processFile.ts
CHANGED
|
@@ -10,9 +10,12 @@ export function processFile(file: string, content: string) {
|
|
|
10
10
|
if (parser === "svelte") content = parseSvelte(content);
|
|
11
11
|
if (parser === "astro") content = parseAstro(content);
|
|
12
12
|
|
|
13
|
-
const virtualFile =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const virtualFile =
|
|
14
|
+
file.endsWith(".vue") ||
|
|
15
|
+
file.endsWith(".svelte") ||
|
|
16
|
+
file.endsWith(".astro")
|
|
17
|
+
? file + ".tsx"
|
|
18
|
+
: file;
|
|
16
19
|
|
|
17
20
|
return { content, virtualFile };
|
|
18
21
|
}
|