@eventra_dev/eventra-cli 0.0.17 → 0.0.18
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 +89 -0
- package/dist/commands/check.js +116 -0
- package/dist/index.js +7 -0
- package/package.json +1 -1
- package/src/commands/check.ts +172 -0
- package/src/index.ts +8 -0
package/README.md
CHANGED
|
@@ -64,6 +64,7 @@ npx eventra init
|
|
|
64
64
|
```bash
|
|
65
65
|
eventra init
|
|
66
66
|
eventra sync
|
|
67
|
+
eventra check
|
|
67
68
|
eventra send
|
|
68
69
|
```
|
|
69
70
|
|
|
@@ -121,6 +122,94 @@ analytics.trackFeature("user_signup")
|
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
|
125
|
+
## eventra check
|
|
126
|
+
|
|
127
|
+
Validate events in your codebase without modifying `eventra.json`.
|
|
128
|
+
|
|
129
|
+
This command is designed for CI/CD pipelines and ensures:
|
|
130
|
+
|
|
131
|
+
- No new events are introduced unintentionally
|
|
132
|
+
- All dynamic events are resolved via aliases
|
|
133
|
+
- Event naming stays consistent
|
|
134
|
+
|
|
135
|
+
``` bash
|
|
136
|
+
eventra check
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
### What it checks
|
|
142
|
+
|
|
143
|
+
#### 1. New events
|
|
144
|
+
|
|
145
|
+
If your code contains events not listed in `eventra.json`:
|
|
146
|
+
|
|
147
|
+
``` ts
|
|
148
|
+
trackFeature("new_event")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
CLI output:
|
|
152
|
+
|
|
153
|
+
New events detected:
|
|
154
|
+
+ new_event
|
|
155
|
+
❌ Event check failed
|
|
156
|
+
|
|
157
|
+
------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
#### 2. Unresolved dynamic events
|
|
160
|
+
|
|
161
|
+
If your code contains dynamic values without alias:
|
|
162
|
+
|
|
163
|
+
``` ts
|
|
164
|
+
trackFeature(eventName)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
CLI output:
|
|
168
|
+
|
|
169
|
+
Unresolved dynamic events:
|
|
170
|
+
- eventName
|
|
171
|
+
❌ Event check failed
|
|
172
|
+
|
|
173
|
+
------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
### CI usage
|
|
176
|
+
|
|
177
|
+
``` bash
|
|
178
|
+
pnpm eventra check
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### GitHub Actions example
|
|
182
|
+
|
|
183
|
+
``` yaml
|
|
184
|
+
- run: pnpm eventra check
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
### Skipping dynamic events
|
|
190
|
+
|
|
191
|
+
You can explicitly ignore dynamic variables using aliases:
|
|
192
|
+
|
|
193
|
+
``` json
|
|
194
|
+
{
|
|
195
|
+
"aliases": {
|
|
196
|
+
"eventName": "__skip__"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
### When to use
|
|
204
|
+
|
|
205
|
+
Use `eventra check` to:
|
|
206
|
+
|
|
207
|
+
- enforce event consistency\
|
|
208
|
+
- prevent accidental event creation\
|
|
209
|
+
- validate analytics in pull requests
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
124
213
|
## eventra send
|
|
125
214
|
|
|
126
215
|
Send events to Eventra backend.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.check = check;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
9
|
+
const ts_morph_1 = require("ts-morph");
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const config_1 = require("../utils/config");
|
|
12
|
+
const router_1 = require("../utils/parsers/router");
|
|
13
|
+
const vue_1 = require("../utils/parsers/vue");
|
|
14
|
+
const svelte_1 = require("../utils/parsers/svelte");
|
|
15
|
+
const astro_1 = require("../utils/parsers/astro");
|
|
16
|
+
const track_1 = require("../utils/scanners/track");
|
|
17
|
+
const function_wrappers_1 = require("../utils/scanners/function-wrappers");
|
|
18
|
+
const component_wrappers_1 = require("../utils/scanners/component-wrappers");
|
|
19
|
+
async function check() {
|
|
20
|
+
const config = await (0, config_1.loadConfig)();
|
|
21
|
+
if (!config) {
|
|
22
|
+
console.log(chalk_1.default.red("eventra.json not found"));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
console.log(chalk_1.default.blue("Checking events..."));
|
|
26
|
+
const project = new ts_morph_1.Project();
|
|
27
|
+
const knownEvents = new Set(config.events ?? []);
|
|
28
|
+
const aliases = config.aliases ?? {};
|
|
29
|
+
const foundEvents = new Set();
|
|
30
|
+
const newEvents = new Set();
|
|
31
|
+
const unresolvedDynamic = new Set();
|
|
32
|
+
const files = await (0, fast_glob_1.default)(config.sync.include, {
|
|
33
|
+
ignore: config.sync.exclude
|
|
34
|
+
});
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
try {
|
|
37
|
+
// skip huge files
|
|
38
|
+
const stat = await promises_1.default.stat(file);
|
|
39
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
40
|
+
console.log(chalk_1.default.gray(`Skipped large file: ${file}`));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const parser = (0, router_1.detectParser)(file);
|
|
44
|
+
let content = await promises_1.default.readFile(file, "utf-8");
|
|
45
|
+
if (parser === "vue")
|
|
46
|
+
content = (0, vue_1.parseVue)(content);
|
|
47
|
+
if (parser === "svelte")
|
|
48
|
+
content = (0, svelte_1.parseSvelte)(content);
|
|
49
|
+
if (parser === "astro")
|
|
50
|
+
content = (0, astro_1.parseAstro)(content);
|
|
51
|
+
const virtualFile = parser === "ts" ? file : file + ".tsx";
|
|
52
|
+
const source = project.createSourceFile(virtualFile, content, { overwrite: true });
|
|
53
|
+
const found = [
|
|
54
|
+
...(0, track_1.scanTrack)(source, config),
|
|
55
|
+
...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? []),
|
|
56
|
+
...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [])
|
|
57
|
+
];
|
|
58
|
+
for (const event of found) {
|
|
59
|
+
const { value, dynamic } = event;
|
|
60
|
+
if (!value)
|
|
61
|
+
continue;
|
|
62
|
+
// alias skip
|
|
63
|
+
if (aliases[value] === "__skip__") {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// alias resolve
|
|
67
|
+
if (aliases[value]) {
|
|
68
|
+
foundEvents.add(aliases[value]);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// dynamic without alias
|
|
72
|
+
if (dynamic) {
|
|
73
|
+
unresolvedDynamic.add(value);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// static
|
|
77
|
+
foundEvents.add(value);
|
|
78
|
+
}
|
|
79
|
+
// clear memory
|
|
80
|
+
project.removeSourceFile(source);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
console.log(chalk_1.default.gray(`Skipped file (error): ${file}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// find new events
|
|
87
|
+
for (const event of foundEvents) {
|
|
88
|
+
if (!knownEvents.has(event)) {
|
|
89
|
+
newEvents.add(event);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let hasError = false;
|
|
93
|
+
// dynamic
|
|
94
|
+
if (unresolvedDynamic.size > 0) {
|
|
95
|
+
hasError = true;
|
|
96
|
+
console.log(chalk_1.default.red("\nUnresolved dynamic events:"));
|
|
97
|
+
[...unresolvedDynamic]
|
|
98
|
+
.sort()
|
|
99
|
+
.forEach((e) => console.log(chalk_1.default.red(`- ${e}`)));
|
|
100
|
+
}
|
|
101
|
+
// new events
|
|
102
|
+
if (newEvents.size > 0) {
|
|
103
|
+
hasError = true;
|
|
104
|
+
console.log(chalk_1.default.red("\nNew events detected:"));
|
|
105
|
+
[...newEvents]
|
|
106
|
+
.sort()
|
|
107
|
+
.forEach((e) => console.log(chalk_1.default.red(`+ ${e}`)));
|
|
108
|
+
}
|
|
109
|
+
if (hasError) {
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log(chalk_1.default.red("Event check failed"));
|
|
112
|
+
console.log(chalk_1.default.red(`dynamic: ${unresolvedDynamic.size}, new: ${newEvents.size}`));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
console.log(chalk_1.default.green("\nAll events are valid"));
|
|
116
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -54,6 +54,13 @@ program
|
|
|
54
54
|
const { sync } = await Promise.resolve().then(() => __importStar(require("./commands/sync")));
|
|
55
55
|
await sync();
|
|
56
56
|
});
|
|
57
|
+
program
|
|
58
|
+
.command("check")
|
|
59
|
+
.description("Validate events (CI mode)")
|
|
60
|
+
.action(async () => {
|
|
61
|
+
const { check } = await Promise.resolve().then(() => __importStar(require("./commands/check")));
|
|
62
|
+
await check();
|
|
63
|
+
});
|
|
57
64
|
program
|
|
58
65
|
.command("send")
|
|
59
66
|
.description("Send events")
|
package/package.json
CHANGED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { Project } from "ts-morph";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
|
|
6
|
+
import { loadConfig } from "../utils/config";
|
|
7
|
+
|
|
8
|
+
import { detectParser } from "../utils/parsers/router";
|
|
9
|
+
import { parseVue } from "../utils/parsers/vue";
|
|
10
|
+
import { parseSvelte } from "../utils/parsers/svelte";
|
|
11
|
+
import { parseAstro } from "../utils/parsers/astro";
|
|
12
|
+
|
|
13
|
+
import { scanTrack } from "../utils/scanners/track";
|
|
14
|
+
import { scanFunctionWrappers } from "../utils/scanners/function-wrappers";
|
|
15
|
+
import { scanComponentWrappers } from "../utils/scanners/component-wrappers";
|
|
16
|
+
|
|
17
|
+
import { ExtractedEvent } from "../types";
|
|
18
|
+
|
|
19
|
+
export async function check() {
|
|
20
|
+
const config = await loadConfig();
|
|
21
|
+
|
|
22
|
+
if (!config) {
|
|
23
|
+
console.log(chalk.red("eventra.json not found"));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(chalk.blue("Checking events..."));
|
|
28
|
+
|
|
29
|
+
const project = new Project();
|
|
30
|
+
|
|
31
|
+
const knownEvents = new Set(config.events ?? []);
|
|
32
|
+
const aliases = config.aliases ?? {};
|
|
33
|
+
|
|
34
|
+
const foundEvents = new Set<string>();
|
|
35
|
+
const newEvents = new Set<string>();
|
|
36
|
+
const unresolvedDynamic = new Set<string>();
|
|
37
|
+
|
|
38
|
+
const files = await fg(config.sync.include, {
|
|
39
|
+
ignore: config.sync.exclude
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
try {
|
|
44
|
+
// skip huge files
|
|
45
|
+
const stat = await fs.stat(file);
|
|
46
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
47
|
+
console.log(chalk.gray(`Skipped large file: ${file}`));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const parser = detectParser(file);
|
|
52
|
+
|
|
53
|
+
let content = await fs.readFile(file, "utf-8");
|
|
54
|
+
|
|
55
|
+
if (parser === "vue") content = parseVue(content);
|
|
56
|
+
if (parser === "svelte") content = parseSvelte(content);
|
|
57
|
+
if (parser === "astro") content = parseAstro(content);
|
|
58
|
+
|
|
59
|
+
const virtualFile =
|
|
60
|
+
parser === "ts" ? file : file + ".tsx";
|
|
61
|
+
|
|
62
|
+
const source = project.createSourceFile(
|
|
63
|
+
virtualFile,
|
|
64
|
+
content,
|
|
65
|
+
{ overwrite: true }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const found: ExtractedEvent[] = [
|
|
69
|
+
...scanTrack(source, config),
|
|
70
|
+
...scanFunctionWrappers(
|
|
71
|
+
source,
|
|
72
|
+
config.functionWrappers ?? []
|
|
73
|
+
),
|
|
74
|
+
...scanComponentWrappers(
|
|
75
|
+
source,
|
|
76
|
+
config.wrappers ?? []
|
|
77
|
+
)
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const event of found) {
|
|
81
|
+
const { value, dynamic } = event;
|
|
82
|
+
|
|
83
|
+
if (!value) continue;
|
|
84
|
+
|
|
85
|
+
// alias skip
|
|
86
|
+
if (aliases[value] === "__skip__") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// alias resolve
|
|
91
|
+
if (aliases[value]) {
|
|
92
|
+
foundEvents.add(aliases[value]);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// dynamic without alias
|
|
97
|
+
if (dynamic) {
|
|
98
|
+
unresolvedDynamic.add(value);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// static
|
|
103
|
+
foundEvents.add(value);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// clear memory
|
|
107
|
+
project.removeSourceFile(source);
|
|
108
|
+
|
|
109
|
+
} catch {
|
|
110
|
+
console.log(
|
|
111
|
+
chalk.gray(`Skipped file (error): ${file}`)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// find new events
|
|
117
|
+
for (const event of foundEvents) {
|
|
118
|
+
if (!knownEvents.has(event)) {
|
|
119
|
+
newEvents.add(event);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let hasError = false;
|
|
124
|
+
|
|
125
|
+
// dynamic
|
|
126
|
+
if (unresolvedDynamic.size > 0) {
|
|
127
|
+
hasError = true;
|
|
128
|
+
|
|
129
|
+
console.log(
|
|
130
|
+
chalk.red("\nUnresolved dynamic events:")
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
[...unresolvedDynamic]
|
|
134
|
+
.sort()
|
|
135
|
+
.forEach((e) =>
|
|
136
|
+
console.log(chalk.red(`- ${e}`))
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// new events
|
|
141
|
+
if (newEvents.size > 0) {
|
|
142
|
+
hasError = true;
|
|
143
|
+
|
|
144
|
+
console.log(
|
|
145
|
+
chalk.red("\nNew events detected:")
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
[...newEvents]
|
|
149
|
+
.sort()
|
|
150
|
+
.forEach((e) =>
|
|
151
|
+
console.log(chalk.red(`+ ${e}`))
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (hasError) {
|
|
156
|
+
console.log("");
|
|
157
|
+
|
|
158
|
+
console.log(chalk.red("Event check failed"));
|
|
159
|
+
|
|
160
|
+
console.log(
|
|
161
|
+
chalk.red(
|
|
162
|
+
`dynamic: ${unresolvedDynamic.size}, new: ${newEvents.size}`
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(
|
|
170
|
+
chalk.green("\nAll events are valid")
|
|
171
|
+
);
|
|
172
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,14 @@ program
|
|
|
25
25
|
await sync();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
program
|
|
29
|
+
.command("check")
|
|
30
|
+
.description("Validate events (CI mode)")
|
|
31
|
+
.action(async () => {
|
|
32
|
+
const { check } = await import("./commands/check");
|
|
33
|
+
await check();
|
|
34
|
+
});
|
|
35
|
+
|
|
28
36
|
program
|
|
29
37
|
.command("send")
|
|
30
38
|
.description("Send events")
|