@eventra_dev/eventra-cli 0.0.16 → 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 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")
@@ -14,6 +14,7 @@ function normalizeConfig(config) {
14
14
  return {
15
15
  apiKey: config.apiKey ?? "",
16
16
  events: config.events ?? [],
17
+ endpoint: config.endpoint ?? "",
17
18
  wrappers: config.wrappers ?? [],
18
19
  functionWrappers: config.functionWrappers ?? [],
19
20
  aliases: config.aliases ?? {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -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")
@@ -10,6 +10,7 @@ export function normalizeConfig(
10
10
  return {
11
11
  apiKey: config.apiKey ?? "",
12
12
  events: config.events ?? [],
13
+ endpoint: config.endpoint ?? "",
13
14
  wrappers: config.wrappers ?? [],
14
15
  functionWrappers:
15
16
  config.functionWrappers ?? [],