@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.
- package/dist/commands/init.js +77 -12
- package/dist/commands/sync.js +42 -8
- package/dist/utils/extract.js +61 -37
- package/dist/utils/scanners/component-wrappers.js +15 -14
- package/dist/utils/scanners/function-wrappers.js +31 -5
- package/package.json +1 -1
- package/src/commands/init.ts +132 -28
- package/src/commands/sync.ts +74 -29
- package/src/types.ts +7 -1
- package/src/utils/extract.ts +76 -53
- package/src/utils/scanners/component-wrappers.ts +23 -33
- package/src/utils/scanners/function-wrappers.ts +62 -13
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
|
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("
|
|
108
|
+
console.log(chalk_1.default.green("\neventra.json created"));
|
|
109
|
+
console.log(chalk_1.default.gray("\nRun `eventra sync`"));
|
|
45
110
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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);
|
package/dist/utils/extract.js
CHANGED
|
@@ -1,44 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.extractExpression = extractExpression;
|
|
4
4
|
const ts_morph_1 = require("ts-morph");
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
return {
|
|
17
|
+
value: expr
|
|
18
|
+
.getText()
|
|
19
|
+
.replace(/`/g, ""),
|
|
20
|
+
dynamic: false
|
|
21
|
+
};
|
|
35
22
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
const expr = init
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
17
|
-
if (
|
|
18
|
-
events.add(
|
|
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
package/src/commands/init.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
-
"
|
|
158
|
+
"\neventra.json created"
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
console.log(
|
|
163
|
+
chalk.gray(
|
|
164
|
+
"\nRun `eventra sync`"
|
|
61
165
|
)
|
|
62
166
|
);
|
|
63
167
|
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -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
|
-
|
|
87
|
-
(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/src/utils/extract.ts
CHANGED
|
@@ -1,74 +1,97 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CallExpression,
|
|
3
2
|
Node,
|
|
4
|
-
|
|
5
|
-
SyntaxKind,
|
|
3
|
+
SyntaxKind
|
|
6
4
|
} from "ts-morph";
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
call: CallExpression,
|
|
10
|
-
path: string
|
|
11
|
-
): string | null {
|
|
12
|
-
const parts = path.split(".");
|
|
6
|
+
import { ExtractedEvent } from "../types";
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
export function extractExpression(
|
|
9
|
+
expr: Node
|
|
10
|
+
): ExtractedEvent | null {
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
SyntaxKind.NoSubstitutionTemplateLiteral
|
|
55
|
+
Node.isIdentifier(expr)
|
|
53
56
|
) {
|
|
54
|
-
return
|
|
55
|
-
.getText()
|
|
56
|
-
|
|
57
|
+
return {
|
|
58
|
+
value: expr.getText(),
|
|
59
|
+
dynamic: true
|
|
60
|
+
};
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
66
|
-
SyntaxKind.StringLiteral
|
|
69
|
+
Node.isStringLiteral(init)
|
|
67
70
|
) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
80
|
-
SyntaxKind.JsxExpression
|
|
80
|
+
Node.isJsxExpression(init)
|
|
81
81
|
) {
|
|
82
82
|
const expr =
|
|
83
|
-
init
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
34
|
-
|
|
34
|
+
const result =
|
|
35
|
+
extractEventFromArgs(
|
|
35
36
|
call,
|
|
36
|
-
wrapper.
|
|
37
|
+
wrapper.event
|
|
37
38
|
);
|
|
38
39
|
|
|
39
|
-
if (
|
|
40
|
-
events.add(
|
|
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
|
+
}
|