@eventra_dev/eventra-cli 0.0.6 → 0.0.8

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.
@@ -5,29 +5,93 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.init = init;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const fs_extra_1 = __importDefault(require("fs-extra"));
9
- const path_1 = __importDefault(require("path"));
10
8
  const inquirer_1 = __importDefault(require("inquirer"));
11
9
  const config_1 = require("../utils/config");
12
10
  async function init() {
13
11
  console.log(chalk_1.default.blue("Initializing Eventra..."));
14
- const configPath = path_1.default.join(process.cwd(), config_1.CONFIG_NAME);
15
- if (await fs_extra_1.default.pathExists(configPath)) {
16
- console.log(chalk_1.default.yellow("eventra.json already exists"));
17
- return;
18
- }
19
- const answers = await inquirer_1.default.prompt([
12
+ // API KEY
13
+ const { apiKey } = await inquirer_1.default.prompt([
20
14
  {
21
15
  type: "input",
22
16
  name: "apiKey",
23
17
  message: "API Key (optional):"
24
18
  }
25
19
  ]);
20
+ console.log(chalk_1.default.gray("\nEventra automatically detects:"));
21
+ console.log(chalk_1.default.gray("• track('event')"));
22
+ console.log(chalk_1.default.gray("• tracker.track('event')"));
23
+ const wrappers = [];
24
+ const functionWrappers = [];
25
+ // COMPONENT WRAPPERS
26
+ console.log(chalk_1.default.blue("\nComponent wrappers"));
27
+ let addComponent = true;
28
+ while (addComponent) {
29
+ const { useWrapper } = await inquirer_1.default.prompt([
30
+ {
31
+ type: "confirm",
32
+ name: "useWrapper",
33
+ message: "Add component wrapper?",
34
+ default: false
35
+ }
36
+ ]);
37
+ if (!useWrapper)
38
+ break;
39
+ const { name, prop } = await inquirer_1.default.prompt([
40
+ {
41
+ type: "input",
42
+ name: "name",
43
+ message: "Component name:",
44
+ validate: (v) => v ? true : "Required"
45
+ },
46
+ {
47
+ type: "input",
48
+ name: "prop",
49
+ message: "Event prop:",
50
+ default: "event"
51
+ }
52
+ ]);
53
+ wrappers.push({
54
+ name,
55
+ prop
56
+ });
57
+ }
58
+ // FUNCTION WRAPPERS
59
+ console.log(chalk_1.default.blue("\nFunction wrappers"));
60
+ let addFunction = true;
61
+ while (addFunction) {
62
+ const { useWrapper } = await inquirer_1.default.prompt([
63
+ {
64
+ type: "confirm",
65
+ name: "useWrapper",
66
+ message: "Add function wrapper?",
67
+ default: false
68
+ }
69
+ ]);
70
+ if (!useWrapper)
71
+ break;
72
+ const { name, event } = await inquirer_1.default.prompt([
73
+ {
74
+ type: "input",
75
+ name: "name",
76
+ message: "Function name:"
77
+ },
78
+ {
79
+ type: "input",
80
+ name: "event",
81
+ message: "Event field (leave empty if string argument):",
82
+ default: ""
83
+ }
84
+ ]);
85
+ functionWrappers.push({
86
+ name,
87
+ event: event || undefined
88
+ });
89
+ }
26
90
  const config = {
27
- apiKey: answers.apiKey || "",
91
+ apiKey,
28
92
  events: [],
29
- wrappers: [],
30
- functionWrappers: [],
93
+ wrappers,
94
+ functionWrappers,
31
95
  sync: {
32
96
  include: [
33
97
  "**/*.{ts,tsx,js,jsx,vue,svelte,astro}"
@@ -41,5 +105,6 @@ async function init() {
41
105
  }
42
106
  };
43
107
  await (0, config_1.saveConfig)(config);
44
- console.log(chalk_1.default.green("eventra.json created"));
108
+ console.log(chalk_1.default.green("\neventra.json created"));
109
+ console.log(chalk_1.default.gray("\nRun `eventra sync`"));
45
110
  }
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
9
  const ts_morph_1 = require("ts-morph");
10
10
  const promises_1 = __importDefault(require("fs/promises"));
11
+ const inquirer_1 = __importDefault(require("inquirer"));
11
12
  const config_1 = require("../utils/config");
12
13
  const router_1 = require("../utils/parsers/router");
13
14
  const vue_1 = require("../utils/parsers/vue");
@@ -25,14 +26,10 @@ async function sync() {
25
26
  console.log(chalk_1.default.blue("Scanning project..."));
26
27
  const project = new ts_morph_1.Project();
27
28
  const events = new Set();
29
+ const aliases = config.aliases ?? {};
28
30
  const files = await (0, fast_glob_1.default)(config.sync.include, {
29
31
  ignore: config.sync.exclude
30
32
  });
31
- const functionWrappers = (config.functionWrappers ?? []).map((w) => ({
32
- ...w,
33
- path: w.path ?? "0"
34
- }));
35
- const componentWrappers = config.wrappers ?? [];
36
33
  for (const file of files) {
37
34
  const parser = (0, router_1.detectParser)(file);
38
35
  let content = await promises_1.default.readFile(file, "utf-8");
@@ -46,10 +43,47 @@ async function sync() {
46
43
  ? file
47
44
  : file + ".tsx";
48
45
  const source = project.createSourceFile(virtualFile, content, { overwrite: true });
49
- (0, track_1.scanTrack)(source).forEach((e) => events.add(e));
50
- (0, function_wrappers_1.scanFunctionWrappers)(source, functionWrappers).forEach((e) => events.add(e));
51
- (0, component_wrappers_1.scanComponentWrappers)(source, componentWrappers).forEach((e) => events.add(e));
46
+ const found = [
47
+ ...(0, track_1.scanTrack)(source),
48
+ ...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? []),
49
+ ...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [])
50
+ ];
51
+ for (const event of found) {
52
+ if (!event)
53
+ continue;
54
+ const value = typeof event === "string"
55
+ ? event
56
+ : event.value;
57
+ const dynamic = typeof event === "string"
58
+ ? false
59
+ : event.dynamic;
60
+ // alias exists
61
+ if (aliases[value]) {
62
+ events.add(aliases[value]);
63
+ continue;
64
+ }
65
+ // dynamic event
66
+ if (dynamic) {
67
+ console.log(chalk_1.default.yellow("\nDynamic event detected:"));
68
+ console.log(chalk_1.default.gray(value));
69
+ const { name } = await inquirer_1.default.prompt([
70
+ {
71
+ type: "input",
72
+ name: "name",
73
+ message: "Enter event name:",
74
+ validate: (v) => v
75
+ ? true
76
+ : "Required"
77
+ }
78
+ ]);
79
+ aliases[value] = name;
80
+ events.add(name);
81
+ continue;
82
+ }
83
+ events.add(value);
84
+ }
52
85
  }
86
+ config.aliases = aliases;
53
87
  const list = [...events].sort();
54
88
  config.events = list;
55
89
  await (0, config_1.saveConfig)(config);
@@ -1,44 +1,68 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractEvent = extractEvent;
3
+ exports.extractExpression = extractExpression;
4
4
  const ts_morph_1 = require("ts-morph");
5
- function extractEvent(call, path) {
6
- const parts = path.split(".");
7
- let node = call.getArguments()[Number(parts[0])];
8
- if (!node)
9
- return null;
10
- node = unwrap(node);
11
- for (let i = 1; i < parts.length; i++) {
12
- if (!ts_morph_1.Node.isObjectLiteralExpression(node)) {
13
- return null;
14
- }
15
- const obj = node;
16
- const prop = obj.getProperty(parts[i]);
17
- if (!prop)
18
- return null;
19
- if (!ts_morph_1.Node.isPropertyAssignment(prop)) {
20
- return null;
21
- }
22
- const initializer = prop.getInitializer();
23
- if (!initializer)
24
- return null;
25
- node = unwrap(initializer);
26
- }
27
- if (ts_morph_1.Node.isStringLiteral(node)) {
28
- return node.getLiteralText();
29
- }
30
- if (node.getKind() ===
5
+ function extractExpression(expr) {
6
+ // "signup"
7
+ if (ts_morph_1.Node.isStringLiteral(expr)) {
8
+ return {
9
+ value: expr.getLiteralText(),
10
+ dynamic: false
11
+ };
12
+ }
13
+ // `signup`
14
+ if (expr.getKind() ===
31
15
  ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
32
- return node
33
- .getText()
34
- .replace(/`/g, "");
16
+ return {
17
+ value: expr
18
+ .getText()
19
+ .replace(/`/g, ""),
20
+ dynamic: false
21
+ };
35
22
  }
36
- return null;
37
- }
38
- function unwrap(node) {
39
- let current = node;
40
- while (ts_morph_1.Node.isParenthesizedExpression(current)) {
41
- current = current.getExpression();
23
+ // template `${event}`
24
+ if (ts_morph_1.Node.isTemplateExpression(expr)) {
25
+ return {
26
+ value: expr.getText(),
27
+ dynamic: true
28
+ };
29
+ }
30
+ // ROUTES.APP
31
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
32
+ return {
33
+ value: expr.getText(),
34
+ dynamic: true
35
+ };
36
+ }
37
+ // EVENT
38
+ if (ts_morph_1.Node.isIdentifier(expr)) {
39
+ return {
40
+ value: expr.getText(),
41
+ dynamic: true
42
+ };
42
43
  }
43
- return current;
44
+ // getEvent()
45
+ if (ts_morph_1.Node.isCallExpression(expr)) {
46
+ return {
47
+ value: expr
48
+ .getExpression()
49
+ .getText(),
50
+ dynamic: true
51
+ };
52
+ }
53
+ // condition ? "a" : "b"
54
+ if (ts_morph_1.Node.isConditionalExpression(expr)) {
55
+ return {
56
+ value: expr.getText(),
57
+ dynamic: true
58
+ };
59
+ }
60
+ // array[index]
61
+ if (ts_morph_1.Node.isElementAccessExpression(expr)) {
62
+ return {
63
+ value: expr.getText(),
64
+ dynamic: true
65
+ };
66
+ }
67
+ return null;
44
68
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanComponentWrappers = scanComponentWrappers;
4
4
  const ts_morph_1 = require("ts-morph");
5
+ const extract_1 = require("../extract");
5
6
  function scanComponentWrappers(source, wrappers) {
6
7
  const events = new Set();
7
8
  const elements = [
@@ -32,21 +33,21 @@ function scanComponentWrappers(source, wrappers) {
32
33
  const init = attrNode.getInitializer();
33
34
  if (!init)
34
35
  continue;
35
- if (init.getKind() ===
36
- ts_morph_1.SyntaxKind.StringLiteral) {
37
- const value = init.asKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
38
- events.add(value.getLiteralText());
36
+ // event="signup"
37
+ if (ts_morph_1.Node.isStringLiteral(init)) {
38
+ events.add({
39
+ value: init.getLiteralText(),
40
+ dynamic: false
41
+ });
39
42
  }
40
- if (init.getKind() ===
41
- ts_morph_1.SyntaxKind.JsxExpression) {
42
- const expr = init
43
- .asKindOrThrow(ts_morph_1.SyntaxKind.JsxExpression)
44
- .getExpression();
45
- if (expr?.getKind() ===
46
- ts_morph_1.SyntaxKind.StringLiteral) {
47
- const value = expr.asKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
48
- events.add(value.getLiteralText());
49
- }
43
+ // event={...}
44
+ if (ts_morph_1.Node.isJsxExpression(init)) {
45
+ const expr = init.getExpression();
46
+ if (!expr)
47
+ continue;
48
+ const result = (0, extract_1.extractExpression)(expr);
49
+ if (result)
50
+ events.add(result);
50
51
  }
51
52
  }
52
53
  }
@@ -13,20 +13,18 @@ function scanFunctionWrappers(source, wrappers) {
13
13
  for (const wrapper of wrappers) {
14
14
  if (wrapper.name !== name)
15
15
  continue;
16
- const event = (0, extract_1.extractEvent)(call, wrapper.path ?? "0");
17
- if (event)
18
- events.add(event);
16
+ const result = extractEventFromArgs(call, wrapper.event);
17
+ if (result)
18
+ events.add(result);
19
19
  }
20
20
  }
21
21
  return events;
22
22
  }
23
23
  function getFunctionName(call) {
24
24
  const expression = call.getExpression();
25
- // trackFeature()
26
25
  if (ts_morph_1.Node.isIdentifier(expression)) {
27
26
  return expression.getText();
28
27
  }
29
- // analytics.trackFeature()
30
28
  if (ts_morph_1.Node.isPropertyAccessExpression(expression)) {
31
29
  return getDeepName(expression);
32
30
  }
@@ -43,3 +41,31 @@ function getDeepName(node) {
43
41
  }
44
42
  return name;
45
43
  }
44
+ function extractEventFromArgs(call, event) {
45
+ const args = call.getArguments();
46
+ for (let i = 0; i < args.length; i++) {
47
+ const arg = args[i];
48
+ // track("event")
49
+ if (!event) {
50
+ const result = (0, extract_1.extractExpression)(arg);
51
+ if (result)
52
+ return result;
53
+ }
54
+ // track({ event: "..." })
55
+ if (event) {
56
+ const obj = arg.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
57
+ if (!obj)
58
+ continue;
59
+ const prop = obj.getProperty(event);
60
+ if (!prop)
61
+ continue;
62
+ if (ts_morph_1.Node.isPropertyAssignment(prop)) {
63
+ const init = prop.getInitializer();
64
+ if (!init)
65
+ continue;
66
+ return (0, extract_1.extractExpression)(init);
67
+ }
68
+ }
69
+ }
70
+ return null;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,45 +1,143 @@
1
1
  import chalk from "chalk";
2
- import fs from "fs-extra";
3
- import path from "path";
4
2
  import inquirer from "inquirer";
5
- import {
6
- CONFIG_NAME,
7
- saveConfig
8
- } from "../utils/config";
3
+
4
+ import { saveConfig } from "../utils/config";
5
+ import {ComponentWrapper, FunctionWrapper} from "../types";
9
6
 
10
7
  export async function init() {
11
8
  console.log(
12
9
  chalk.blue("Initializing Eventra...")
13
10
  );
14
11
 
15
- const configPath = path.join(
16
- process.cwd(),
17
- CONFIG_NAME
12
+ // API KEY
13
+ const { apiKey } =
14
+ await inquirer.prompt([
15
+ {
16
+ type: "input",
17
+ name: "apiKey",
18
+ message:
19
+ "API Key (optional):"
20
+ }
21
+ ]);
22
+
23
+ console.log(
24
+ chalk.gray(
25
+ "\nEventra automatically detects:"
26
+ )
27
+ );
28
+
29
+ console.log(
30
+ chalk.gray("• track('event')")
31
+ );
32
+
33
+ console.log(
34
+ chalk.gray(
35
+ "• tracker.track('event')"
36
+ )
18
37
  );
19
38
 
20
- if (await fs.pathExists(configPath)) {
21
- console.log(
22
- chalk.yellow(
23
- "eventra.json already exists"
24
- )
25
- );
26
- return;
39
+ const wrappers: ComponentWrapper[] = [];
40
+ const functionWrappers: FunctionWrapper[] = [];
41
+
42
+
43
+ // COMPONENT WRAPPERS
44
+ console.log(
45
+ chalk.blue(
46
+ "\nComponent wrappers"
47
+ )
48
+ );
49
+
50
+ let addComponent = true;
51
+
52
+ while (addComponent) {
53
+ const { useWrapper } =
54
+ await inquirer.prompt([
55
+ {
56
+ type: "confirm",
57
+ name: "useWrapper",
58
+ message:
59
+ "Add component wrapper?",
60
+ default: false
61
+ }
62
+ ]);
63
+
64
+ if (!useWrapper) break;
65
+
66
+ const { name, prop } =
67
+ await inquirer.prompt([
68
+ {
69
+ type: "input",
70
+ name: "name",
71
+ message: "Component name:",
72
+ validate: (v) =>
73
+ v ? true : "Required"
74
+ },
75
+ {
76
+ type: "input",
77
+ name: "prop",
78
+ message:
79
+ "Event prop:",
80
+ default: "event"
81
+ }
82
+ ]);
83
+
84
+ wrappers.push({
85
+ name,
86
+ prop
87
+ });
27
88
  }
28
89
 
29
- const answers = await inquirer.prompt([
30
- {
31
- type: "input",
32
- name: "apiKey",
33
- message:
34
- "API Key (optional):"
35
- }
36
- ]);
90
+ // FUNCTION WRAPPERS
91
+ console.log(
92
+ chalk.blue(
93
+ "\nFunction wrappers"
94
+ )
95
+ );
96
+
97
+ let addFunction = true;
98
+
99
+ while (addFunction) {
100
+ const { useWrapper } =
101
+ await inquirer.prompt([
102
+ {
103
+ type: "confirm",
104
+ name: "useWrapper",
105
+ message:
106
+ "Add function wrapper?",
107
+ default: false
108
+ }
109
+ ]);
110
+
111
+ if (!useWrapper) break;
112
+
113
+ const { name, event } =
114
+ await inquirer.prompt([
115
+ {
116
+ type: "input",
117
+ name: "name",
118
+ message:
119
+ "Function name:"
120
+ },
121
+ {
122
+ type: "input",
123
+ name: "event",
124
+ message:
125
+ "Event field (leave empty if string argument):",
126
+ default: ""
127
+ }
128
+ ]);
129
+
130
+ functionWrappers.push({
131
+ name,
132
+ event: event || undefined
133
+ });
134
+ }
37
135
 
38
136
  const config = {
39
- apiKey: answers.apiKey || "",
137
+ apiKey,
40
138
  events: [],
41
- wrappers: [],
42
- functionWrappers: [],
139
+ wrappers,
140
+ functionWrappers,
43
141
  sync: {
44
142
  include: [
45
143
  "**/*.{ts,tsx,js,jsx,vue,svelte,astro}"
@@ -57,7 +155,13 @@ export async function init() {
57
155
 
58
156
  console.log(
59
157
  chalk.green(
60
- "eventra.json created"
158
+ "\neventra.json created"
159
+ )
160
+ );
161
+
162
+ console.log(
163
+ chalk.gray(
164
+ "\nRun `eventra sync`"
61
165
  )
62
166
  );
63
167
  }
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import fg from "fast-glob";
3
3
  import { Project } from "ts-morph";
4
4
  import fs from "fs/promises";
5
+ import inquirer from "inquirer";
5
6
 
6
7
  import {
7
8
  loadConfig,
@@ -34,6 +35,9 @@ export async function sync() {
34
35
  const project = new Project();
35
36
  const events = new Set<string>();
36
37
 
38
+ const aliases =
39
+ config.aliases ?? {};
40
+
37
41
  const files = await fg(
38
42
  config.sync.include,
39
43
  {
@@ -41,17 +45,6 @@ export async function sync() {
41
45
  }
42
46
  );
43
47
 
44
- const functionWrappers =
45
- (config.functionWrappers ?? []).map(
46
- (w) => ({
47
- ...w,
48
- path: w.path ?? "0"
49
- })
50
- );
51
-
52
- const componentWrappers =
53
- config.wrappers ?? [];
54
-
55
48
  for (const file of files) {
56
49
  const parser =
57
50
  detectParser(file);
@@ -83,26 +76,78 @@ export async function sync() {
83
76
  { overwrite: true }
84
77
  );
85
78
 
86
- scanTrack(source).forEach(
87
- (e) => events.add(e)
88
- );
89
-
90
-
91
- scanFunctionWrappers(
92
- source,
93
- functionWrappers
94
- ).forEach((e) =>
95
- events.add(e)
96
- );
97
-
98
- scanComponentWrappers(
99
- source,
100
- componentWrappers
101
- ).forEach((e) =>
102
- events.add(e)
103
- );
79
+ const found = [
80
+ ...scanTrack(source),
81
+ ...scanFunctionWrappers(
82
+ source,
83
+ config.functionWrappers ?? []
84
+ ),
85
+ ...scanComponentWrappers(
86
+ source,
87
+ config.wrappers ?? []
88
+ )
89
+ ];
90
+
91
+ for (const event of found) {
92
+ if (!event) continue;
93
+
94
+ const value =
95
+ typeof event === "string"
96
+ ? event
97
+ : event.value;
98
+
99
+ const dynamic =
100
+ typeof event === "string"
101
+ ? false
102
+ : event.dynamic;
103
+
104
+ // alias exists
105
+ if (aliases[value]) {
106
+ events.add(
107
+ aliases[value]
108
+ );
109
+ continue;
110
+ }
111
+
112
+ // dynamic event
113
+ if (dynamic) {
114
+ console.log(
115
+ chalk.yellow(
116
+ "\nDynamic event detected:"
117
+ )
118
+ );
119
+
120
+ console.log(
121
+ chalk.gray(value)
122
+ );
123
+
124
+ const { name } =
125
+ await inquirer.prompt([
126
+ {
127
+ type: "input",
128
+ name: "name",
129
+ message:
130
+ "Enter event name:",
131
+ validate: (v) =>
132
+ v
133
+ ? true
134
+ : "Required"
135
+ }
136
+ ]);
137
+
138
+ aliases[value] = name;
139
+
140
+ events.add(name);
141
+
142
+ continue;
143
+ }
144
+
145
+ events.add(value);
146
+ }
104
147
  }
105
148
 
149
+ config.aliases = aliases;
150
+
106
151
  const list =
107
152
  [...events].sort();
108
153
 
package/src/types.ts CHANGED
@@ -5,7 +5,7 @@ export type ComponentWrapper = {
5
5
 
6
6
  export type FunctionWrapper = {
7
7
  name: string;
8
- path?: string;
8
+ event?: string;
9
9
  };
10
10
 
11
11
  export type EventraConfig = {
@@ -13,8 +13,14 @@ export type EventraConfig = {
13
13
  events: string[];
14
14
  wrappers: ComponentWrapper[];
15
15
  functionWrappers: FunctionWrapper[];
16
+ aliases?: Record<string, string>;
16
17
  sync: {
17
18
  include: string[];
18
19
  exclude: string[];
19
20
  };
20
21
  };
22
+
23
+ export type ExtractedEvent = {
24
+ value: string;
25
+ dynamic: boolean;
26
+ };
@@ -1,74 +1,97 @@
1
1
  import {
2
- CallExpression,
3
2
  Node,
4
- ObjectLiteralExpression,
5
- SyntaxKind,
3
+ SyntaxKind
6
4
  } from "ts-morph";
7
5
 
8
- export function extractEvent(
9
- call: CallExpression,
10
- path: string
11
- ): string | null {
12
- const parts = path.split(".");
6
+ import { ExtractedEvent } from "../types";
13
7
 
14
- let node: Node | undefined =
15
- call.getArguments()[Number(parts[0])];
8
+ export function extractExpression(
9
+ expr: Node
10
+ ): ExtractedEvent | null {
16
11
 
17
- if (!node) return null;
18
-
19
- node = unwrap(node);
20
-
21
- for (let i = 1; i < parts.length; i++) {
22
- if (!Node.isObjectLiteralExpression(node)) {
23
- return null;
24
- }
25
-
26
- const obj: ObjectLiteralExpression =
27
- node;
28
-
29
- const prop =
30
- obj.getProperty(parts[i]);
31
-
32
- if (!prop) return null;
33
-
34
- if (!Node.isPropertyAssignment(prop)) {
35
- return null;
36
- }
37
-
38
- const initializer =
39
- prop.getInitializer();
12
+ // "signup"
13
+ if (Node.isStringLiteral(expr)) {
14
+ return {
15
+ value: expr.getLiteralText(),
16
+ dynamic: false
17
+ };
18
+ }
40
19
 
41
- if (!initializer) return null;
20
+ // `signup`
21
+ if (
22
+ expr.getKind() ===
23
+ SyntaxKind.NoSubstitutionTemplateLiteral
24
+ ) {
25
+ return {
26
+ value: expr
27
+ .getText()
28
+ .replace(/`/g, ""),
29
+ dynamic: false
30
+ };
31
+ }
42
32
 
43
- node = unwrap(initializer);
33
+ // template `${event}`
34
+ if (
35
+ Node.isTemplateExpression(expr)
36
+ ) {
37
+ return {
38
+ value: expr.getText(),
39
+ dynamic: true
40
+ };
44
41
  }
45
42
 
46
- if (Node.isStringLiteral(node)) {
47
- return node.getLiteralText();
43
+ // ROUTES.APP
44
+ if (
45
+ Node.isPropertyAccessExpression(expr)
46
+ ) {
47
+ return {
48
+ value: expr.getText(),
49
+ dynamic: true
50
+ };
48
51
  }
49
52
 
53
+ // EVENT
50
54
  if (
51
- node.getKind() ===
52
- SyntaxKind.NoSubstitutionTemplateLiteral
55
+ Node.isIdentifier(expr)
53
56
  ) {
54
- return node
55
- .getText()
56
- .replace(/`/g, "");
57
+ return {
58
+ value: expr.getText(),
59
+ dynamic: true
60
+ };
57
61
  }
58
62
 
59
- return null;
60
- }
63
+ // getEvent()
64
+ if (
65
+ Node.isCallExpression(expr)
66
+ ) {
67
+ return {
68
+ value:
69
+ expr
70
+ .getExpression()
71
+ .getText(),
72
+ dynamic: true
73
+ };
74
+ }
61
75
 
62
- function unwrap(node: Node): Node {
63
- let current = node;
76
+ // condition ? "a" : "b"
77
+ if (
78
+ Node.isConditionalExpression(expr)
79
+ ) {
80
+ return {
81
+ value: expr.getText(),
82
+ dynamic: true
83
+ };
84
+ }
64
85
 
65
- while (
66
- Node.isParenthesizedExpression(
67
- current
68
- )
69
- ) {
70
- current = current.getExpression();
86
+ // array[index]
87
+ if (
88
+ Node.isElementAccessExpression(expr)
89
+ ) {
90
+ return {
91
+ value: expr.getText(),
92
+ dynamic: true
93
+ };
71
94
  }
72
95
 
73
- return current;
96
+ return null;
74
97
  }
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  SourceFile,
3
3
  SyntaxKind,
4
+ Node
4
5
  } from "ts-morph";
5
6
 
6
- import { ComponentWrapper } from "../../types";
7
+ import { ComponentWrapper, ExtractedEvent } from "../../types";
8
+ import { extractExpression } from "../extract";
7
9
 
8
10
  export function scanComponentWrappers(
9
11
  source: SourceFile,
10
12
  wrappers: ComponentWrapper[]
11
13
  ) {
12
- const events = new Set<string>();
14
+ const events =
15
+ new Set<ExtractedEvent>();
13
16
 
14
17
  const elements = [
15
18
  ...source.getDescendantsOfKind(
@@ -61,44 +64,31 @@ export function scanComponentWrappers(
61
64
 
62
65
  if (!init) continue;
63
66
 
67
+ // event="signup"
64
68
  if (
65
- init.getKind() ===
66
- SyntaxKind.StringLiteral
69
+ Node.isStringLiteral(init)
67
70
  ) {
68
- const value =
69
- init.asKindOrThrow(
70
- SyntaxKind.StringLiteral
71
- );
72
-
73
- events.add(
74
- value.getLiteralText()
75
- );
71
+ events.add({
72
+ value:
73
+ init.getLiteralText(),
74
+ dynamic: false
75
+ });
76
76
  }
77
77
 
78
+ // event={...}
78
79
  if (
79
- init.getKind() ===
80
- SyntaxKind.JsxExpression
80
+ Node.isJsxExpression(init)
81
81
  ) {
82
82
  const expr =
83
- init
84
- .asKindOrThrow(
85
- SyntaxKind.JsxExpression
86
- )
87
- .getExpression();
88
-
89
- if (
90
- expr?.getKind() ===
91
- SyntaxKind.StringLiteral
92
- ) {
93
- const value =
94
- expr.asKindOrThrow(
95
- SyntaxKind.StringLiteral
96
- );
97
-
98
- events.add(
99
- value.getLiteralText()
100
- );
101
- }
83
+ init.getExpression();
84
+
85
+ if (!expr) continue;
86
+
87
+ const result =
88
+ extractExpression(expr);
89
+
90
+ if (result)
91
+ events.add(result);
102
92
  }
103
93
  }
104
94
  }
@@ -6,14 +6,15 @@ import {
6
6
  PropertyAccessExpression,
7
7
  } from "ts-morph";
8
8
 
9
- import { extractEvent } from "../extract";
10
- import { FunctionWrapper } from "../../types";
9
+ import { extractExpression } from "../extract";
10
+ import { FunctionWrapper, ExtractedEvent } from "../../types";
11
11
 
12
12
  export function scanFunctionWrappers(
13
13
  source: SourceFile,
14
14
  wrappers: FunctionWrapper[]
15
15
  ) {
16
- const events = new Set<string>();
16
+ const events =
17
+ new Set<ExtractedEvent>();
17
18
 
18
19
  const calls =
19
20
  source.getDescendantsOfKind(
@@ -30,14 +31,14 @@ export function scanFunctionWrappers(
30
31
  if (wrapper.name !== name)
31
32
  continue;
32
33
 
33
- const event =
34
- extractEvent(
34
+ const result =
35
+ extractEventFromArgs(
35
36
  call,
36
- wrapper.path ?? "0"
37
+ wrapper.event
37
38
  );
38
39
 
39
- if (event)
40
- events.add(event);
40
+ if (result)
41
+ events.add(result);
41
42
  }
42
43
  }
43
44
 
@@ -50,16 +51,12 @@ function getFunctionName(
50
51
  const expression =
51
52
  call.getExpression();
52
53
 
53
- // trackFeature()
54
54
  if (
55
- Node.isIdentifier(
56
- expression
57
- )
55
+ Node.isIdentifier(expression)
58
56
  ) {
59
57
  return expression.getText();
60
58
  }
61
59
 
62
- // analytics.trackFeature()
63
60
  if (
64
61
  Node.isPropertyAccessExpression(
65
62
  expression
@@ -96,3 +93,55 @@ function getDeepName(
96
93
 
97
94
  return name;
98
95
  }
96
+
97
+ function extractEventFromArgs(
98
+ call: CallExpression,
99
+ event?: string
100
+ ): ExtractedEvent | null {
101
+
102
+ const args =
103
+ call.getArguments();
104
+
105
+ for (let i = 0; i < args.length; i++) {
106
+ const arg = args[i];
107
+
108
+ // track("event")
109
+ if (!event) {
110
+ const result =
111
+ extractExpression(arg);
112
+
113
+ if (result)
114
+ return result;
115
+ }
116
+
117
+ // track({ event: "..." })
118
+ if (event) {
119
+ const obj =
120
+ arg.asKind(
121
+ SyntaxKind.ObjectLiteralExpression
122
+ );
123
+
124
+ if (!obj) continue;
125
+
126
+ const prop =
127
+ obj.getProperty(event);
128
+
129
+ if (!prop) continue;
130
+
131
+ if (
132
+ Node.isPropertyAssignment(prop)
133
+ ) {
134
+ const init =
135
+ prop.getInitializer();
136
+
137
+ if (!init) continue;
138
+
139
+ return extractExpression(
140
+ init
141
+ );
142
+ }
143
+ }
144
+ }
145
+
146
+ return null;
147
+ }