@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.
@@ -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
  }
@@ -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
- // 🔥 cross-file
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).forEach(e => events.add(e));
44
+ scanSource(sf, checker, visited, depth + 1)
45
+ .forEach(e => events.add(e));
30
46
  }
31
47
  }
32
48
 
33
- if (target) {
34
- const res = resolveNodeValue(target, checker);
49
+ for (const arg of node.arguments) {
50
+ const res = resolveNodeValue(arg, checker, paramMap);
35
51
 
36
52
  res?.values.forEach(v => {
37
- if (v && v.length > 1 && !v.includes(" ")) {
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
  });
@@ -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
- const config =
47
- await fs.readJSON(
48
- configPath
49
- );
50
-
51
- return normalizeConfig(config);
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
- config: EventraConfig
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
- configPath,
67
- normalized,
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: string
3
- ) {
4
- return content;
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: string
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(".svelte"))
8
- return "svelte";
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
- content: string
3
- ) {
4
- return content;
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
  }
@@ -1,16 +1,26 @@
1
1
  export function parseVue(content: string) {
2
- const scriptMatch = content.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/);
3
- const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
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
- const script = scriptMatch?.[1] ?? "";
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__ = (
@@ -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 = file.endsWith(".vue")
14
- ? file + ".tsx"
15
- : file;
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
  }