@eventra_dev/eventra-cli 0.3.13 → 0.4.2
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 +65 -4
- package/dist/cli/send.js +110 -50
- package/package.json +28 -6
package/README.md
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
# Eventra CLI
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@eventra_dev/eventra-cli"><img alt="npm version" src="https://img.shields.io/npm/v/@eventra_dev/eventra-cli.svg?style=flat-square&color=blue"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@eventra_dev/eventra-cli"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@eventra_dev/eventra-cli.svg?style=flat-square&color=blue"></a>
|
|
10
|
+
<a href="https://github.com/and-1991/eventra-cli/actions/workflows/test.yml"><img alt="tests" src="https://img.shields.io/github/actions/workflow/status/and-1991/eventra-cli/test.yml?branch=main&label=tests&style=flat-square&logo=vitest&logoColor=white"></a>
|
|
11
|
+
<img alt="unit tests" src="https://img.shields.io/badge/unit-52%20passing-brightgreen?style=flat-square&logo=vitest&logoColor=white">
|
|
12
|
+
<img alt="e2e fixtures" src="https://img.shields.io/badge/e2e-12%20fixtures-brightgreen?style=flat-square">
|
|
13
|
+
<img alt="node" src="https://img.shields.io/node/v/@eventra_dev/eventra-cli?style=flat-square&color=darkgreen&logo=node.js&logoColor=white">
|
|
14
|
+
<a href="https://www.typescriptlang.org/"><img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-ready-blue?style=flat-square&logo=typescript&logoColor=white"></a>
|
|
15
|
+
<img alt="license" src="https://img.shields.io/npm/l/@eventra_dev/eventra-cli?style=flat-square&color=lightgrey">
|
|
16
|
+
</p>
|
|
11
17
|
|
|
12
18
|
Eventra CLI statically discovers analytics events from [**@eventra_dev/eventra-sdk**](https://www.npmjs.com/package/@eventra_dev/eventra-sdk) usage in your codebase — including function wrappers and cross-file propagation chains.
|
|
13
19
|
|
|
@@ -151,6 +157,13 @@ Incremental scan with the same rules as `sync`.
|
|
|
151
157
|
|
|
152
158
|
Uploads `events` from config to the Eventra API. Requires `apiKey` (and optionally `endpoint`) in `eventra.json`. Events are POSTed to `POST /api/v1/cli/events` and marked as non-billable on the backend.
|
|
153
159
|
|
|
160
|
+
Network resilience:
|
|
161
|
+
|
|
162
|
+
- Up to 4 attempts with exponential backoff + jitter (capped at 8 s)
|
|
163
|
+
- Retries on 429 and 5xx responses, and on transport errors (timeout / DNS / connection reset)
|
|
164
|
+
- Permanent failure (4xx other than 429) surfaces immediately without retry
|
|
165
|
+
- 10 s timeout per attempt via `AbortController`
|
|
166
|
+
|
|
154
167
|
---
|
|
155
168
|
|
|
156
169
|
## Configuration
|
|
@@ -188,6 +201,54 @@ No runtime execution. No monkey-patching.
|
|
|
188
201
|
|
|
189
202
|
---
|
|
190
203
|
|
|
204
|
+
## Test Coverage
|
|
205
|
+
|
|
206
|
+
Two test layers, **52 unit tests + 12 e2e fixtures + 3 `check` exit-code scenarios**.
|
|
207
|
+
|
|
208
|
+
**Unit tests (vitest)** — 10 suites covering core modules:
|
|
209
|
+
|
|
210
|
+
| Module | Covers |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `ImportGraph` | Forward/reverse edges, cycles, stale-edge cleanup, file removal |
|
|
213
|
+
| `Scheduler` | Batch coalescing, last-write-wins per file, sequential bursts, error propagation |
|
|
214
|
+
| `DocumentRegistry` | Path normalization, version bumping, no-op on identical content, `ensure()` from disk |
|
|
215
|
+
| `CompilerContext` | Stage/update/remove files, `resolveModule` with `tsconfig.json` `paths`, source-file enumeration |
|
|
216
|
+
| `EventraEngine` | Direct calls, SDK isolation, cross-file wrappers, file updates, file removal, wrapper filtering |
|
|
217
|
+
| `processFile` | Script-kind detection, import/export specifier extraction |
|
|
218
|
+
| `extractTemplateExpressions` | Vue/Svelte/Astro attribute patterns |
|
|
219
|
+
| `normalizeConfig` | Sort events, dedupe wrappers, defaults, preserve `apiKey` / `endpoint` / `sync` |
|
|
220
|
+
| `buildConfigFromScan` | Replace events + wrappers, preserve everything else |
|
|
221
|
+
| `hash` | Stability and uniqueness |
|
|
222
|
+
|
|
223
|
+
**End-to-end fixtures** — 12 isolated TS projects scanned via `eventra sync`:
|
|
224
|
+
|
|
225
|
+
| Fixture | Covers |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `sdk/direct` | Plain `tracker.track()` calls |
|
|
228
|
+
| `frontend/react`, `frontend/next`, `frontend/vue` | Framework-specific code shapes |
|
|
229
|
+
| `backend/node`, `backend/express`, `backend/nest` | Backend wrappers and middleware chains |
|
|
230
|
+
| `wrappers/function` | Local wrapper functions, methods, objects, ternaries, templates |
|
|
231
|
+
| `wrappers/barrel` | `export * from "./tracker"` re-exports |
|
|
232
|
+
| `wrappers/default-export` | `export default function trackFeature` propagation |
|
|
233
|
+
| `wrappers/path-aliases` | `tsconfig.json` `paths` mapping (`@app/*`) |
|
|
234
|
+
| `watch-incremental` | Engine state across sequential file updates (matches `sync` output) |
|
|
235
|
+
|
|
236
|
+
**`eventra check` exit-code scenarios:**
|
|
237
|
+
|
|
238
|
+
- Drift → exit `1`
|
|
239
|
+
- `--fix` writes scan results into `eventra.json` → exit `0`
|
|
240
|
+
- Parity (no drift) → exit `0`
|
|
241
|
+
|
|
242
|
+
Run locally:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pnpm --filter @eventra_dev/eventra-cli test # unit + e2e + exit codes
|
|
246
|
+
pnpm --filter @eventra_dev/eventra-cli test:unit # vitest only
|
|
247
|
+
pnpm --filter @eventra_dev/eventra-cli test:e2e # fixtures + check exit codes
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
191
252
|
## License
|
|
192
253
|
|
|
193
254
|
MIT
|
package/dist/cli/send.js
CHANGED
|
@@ -9,6 +9,53 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
9
9
|
const config_1 = require("../config/config");
|
|
10
10
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
11
11
|
const CLI_VERSION = package_json_1.default.version;
|
|
12
|
+
// retry/backoff tuning (mirrors SDK defaults)
|
|
13
|
+
const MAX_ATTEMPTS = 4;
|
|
14
|
+
const BASE_DELAY_MS = 500;
|
|
15
|
+
const MAX_DELAY_MS = 8000;
|
|
16
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
17
|
+
// sleep helper for backoff
|
|
18
|
+
function sleep(ms) {
|
|
19
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
20
|
+
}
|
|
21
|
+
// exponential backoff with jitter (capped)
|
|
22
|
+
function backoffMs(attempt) {
|
|
23
|
+
const exp = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);
|
|
24
|
+
return exp * (0.5 + Math.random() * 0.5);
|
|
25
|
+
}
|
|
26
|
+
// single delivery attempt with abort timeout
|
|
27
|
+
async function attemptDelivery(endpoint, apiKey, payload) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(endpoint, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
"x-api-key": apiKey,
|
|
36
|
+
},
|
|
37
|
+
body: payload,
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
const data = (await res.json());
|
|
42
|
+
return { ok: true, data };
|
|
43
|
+
}
|
|
44
|
+
let body;
|
|
45
|
+
try {
|
|
46
|
+
body = await res.text();
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* ignore */
|
|
50
|
+
}
|
|
51
|
+
// 429 and 5xx are transient — worth retrying
|
|
52
|
+
const retryable = res.status === 429 || res.status >= 500;
|
|
53
|
+
return { ok: false, status: res.status, body, retryable };
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
12
59
|
async function send() {
|
|
13
60
|
const config = await (0, config_1.loadConfig)();
|
|
14
61
|
const endpoint = config?.endpoint || process.env.EVENTRA_ENDPOINT;
|
|
@@ -48,63 +95,76 @@ async function send() {
|
|
|
48
95
|
}
|
|
49
96
|
console.log("");
|
|
50
97
|
console.log(chalk_1.default.blue(`Sending ${config.events.length} events...`));
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
signal: controller.signal
|
|
69
|
-
});
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
console.log(chalk_1.default.red(`Request failed (${res.status})`));
|
|
72
|
-
try {
|
|
73
|
-
const text = await res.text();
|
|
74
|
-
console.log(chalk_1.default.gray(text));
|
|
98
|
+
const payload = JSON.stringify({
|
|
99
|
+
events: config.events,
|
|
100
|
+
cli: {
|
|
101
|
+
name: "@eventra_dev/eventra-cli",
|
|
102
|
+
version: CLI_VERSION,
|
|
103
|
+
runtime: "node",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// attempt delivery with retry on 429/5xx/network errors
|
|
107
|
+
let data;
|
|
108
|
+
let lastFailure = null;
|
|
109
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
110
|
+
try {
|
|
111
|
+
const outcome = await attemptDelivery(endpoint, apiKey, payload);
|
|
112
|
+
if (outcome.ok) {
|
|
113
|
+
data = outcome.data;
|
|
114
|
+
break;
|
|
75
115
|
}
|
|
76
|
-
|
|
116
|
+
if (!outcome.retryable) {
|
|
117
|
+
// permanent failure — surface immediately
|
|
118
|
+
console.log(chalk_1.default.red(`Request failed (${outcome.status})`));
|
|
119
|
+
if (outcome.body)
|
|
120
|
+
console.log(chalk_1.default.gray(outcome.body));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
lastFailure = { status: outcome.status, body: outcome.body };
|
|
124
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
125
|
+
const delay = backoffMs(attempt);
|
|
126
|
+
console.log(chalk_1.default.yellow(`Attempt ${attempt + 1}/${MAX_ATTEMPTS} failed (${outcome.status}). Retrying in ${Math.round(delay)} ms...`));
|
|
127
|
+
await sleep(delay);
|
|
77
128
|
}
|
|
78
|
-
return;
|
|
79
129
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
catch (err) {
|
|
131
|
+
lastFailure = { error: err };
|
|
132
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
133
|
+
const delay = backoffMs(attempt);
|
|
134
|
+
console.log(chalk_1.default.yellow(`Attempt ${attempt + 1}/${MAX_ATTEMPTS} failed (network error). Retrying in ${Math.round(delay)} ms...`));
|
|
135
|
+
await sleep(delay);
|
|
136
|
+
}
|
|
86
137
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
}
|
|
139
|
+
if (!data) {
|
|
140
|
+
console.log(chalk_1.default.red("All retries exhausted"));
|
|
141
|
+
if (lastFailure?.status) {
|
|
142
|
+
console.log(chalk_1.default.gray(`Last status: ${lastFailure.status}`));
|
|
143
|
+
if (lastFailure.body)
|
|
144
|
+
console.log(chalk_1.default.gray(lastFailure.body));
|
|
91
145
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log("");
|
|
95
|
-
console.log(chalk_1.default.yellow("Events queued for processing (~2 min)"));
|
|
96
|
-
console.log(chalk_1.default.gray("They will appear in dashboard shortly"));
|
|
146
|
+
else if (lastFailure?.error instanceof Error) {
|
|
147
|
+
console.log(chalk_1.default.gray(lastFailure.error.message));
|
|
97
148
|
}
|
|
98
|
-
|
|
99
|
-
console.log(chalk_1.default.gray(`Sent ${config.events.length} events`));
|
|
149
|
+
return;
|
|
100
150
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
151
|
+
console.log(chalk_1.default.green("Events registered successfully"));
|
|
152
|
+
// created events
|
|
153
|
+
if (data.created?.length) {
|
|
154
|
+
console.log(chalk_1.default.green("\nNew events:"));
|
|
155
|
+
data.created.forEach((e) => console.log(chalk_1.default.green(`+ ${e}`)));
|
|
106
156
|
}
|
|
107
|
-
|
|
108
|
-
|
|
157
|
+
// existing events
|
|
158
|
+
if (data.existing?.length) {
|
|
159
|
+
console.log(chalk_1.default.gray("\nExisting events:"));
|
|
160
|
+
data.existing.forEach((e) => console.log(chalk_1.default.gray(`• ${e}`)));
|
|
161
|
+
}
|
|
162
|
+
// processing notice
|
|
163
|
+
if (data.created?.length) {
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log(chalk_1.default.yellow("Events queued for processing (~2 min)"));
|
|
166
|
+
console.log(chalk_1.default.gray("They will appear in dashboard shortly"));
|
|
109
167
|
}
|
|
168
|
+
console.log("");
|
|
169
|
+
console.log(chalk_1.default.gray(`Sent ${config.events.length} events`));
|
|
110
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventra_dev/eventra-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Static analytics event extraction CLI powered by the TypeScript compiler API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"type": "git",
|
|
18
18
|
"url": "https://github.com/and-1991/eventra-cli"
|
|
19
19
|
},
|
|
20
|
-
|
|
21
20
|
"homepage": "https://eventra.dev",
|
|
22
21
|
"engines": {
|
|
23
22
|
"node": ">=18"
|
|
@@ -25,7 +24,9 @@
|
|
|
25
24
|
"scripts": {
|
|
26
25
|
"build": "tsc -p tsconfig.json",
|
|
27
26
|
"dev": "tsx src/index.ts",
|
|
28
|
-
"test": "pnpm run clean && pnpm run build && tsx tests/run.ts",
|
|
27
|
+
"test": "pnpm run clean && pnpm run build && pnpm run test:unit && tsx tests/run.ts",
|
|
28
|
+
"test:unit": "vitest run",
|
|
29
|
+
"test:e2e": "pnpm run clean && pnpm run build && tsx tests/run.ts",
|
|
29
30
|
"clean": "rm -rf dist"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
@@ -42,18 +43,39 @@
|
|
|
42
43
|
"@types/inquirer": "^9.0.9",
|
|
43
44
|
"@types/node": "^20.0.0",
|
|
44
45
|
"cross-spawn": "^7.0.6",
|
|
45
|
-
"tsx": "^4.7.0"
|
|
46
|
+
"tsx": "^4.7.0",
|
|
47
|
+
"vitest": "^4.1.7"
|
|
46
48
|
},
|
|
47
49
|
"keywords": [
|
|
48
50
|
"analytics",
|
|
51
|
+
"analytics-cli",
|
|
49
52
|
"event-tracking",
|
|
53
|
+
"feature-usage",
|
|
54
|
+
"feature-tracking",
|
|
55
|
+
"product-analytics",
|
|
56
|
+
"telemetry",
|
|
57
|
+
"tracking",
|
|
58
|
+
"events",
|
|
59
|
+
"event-catalog",
|
|
60
|
+
"event-discovery",
|
|
50
61
|
"static-analysis",
|
|
62
|
+
"static-code-analysis",
|
|
51
63
|
"typescript",
|
|
64
|
+
"typescript-compiler",
|
|
52
65
|
"compiler-api",
|
|
53
66
|
"ast",
|
|
67
|
+
"code-analysis",
|
|
68
|
+
"drift-detection",
|
|
69
|
+
"ci",
|
|
70
|
+
"ci-cd",
|
|
71
|
+
"lint",
|
|
72
|
+
"watch-mode",
|
|
54
73
|
"cli",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
74
|
+
"command-line",
|
|
75
|
+
"developer-tools",
|
|
76
|
+
"devtools",
|
|
77
|
+
"eventra",
|
|
78
|
+
"eventra-cli"
|
|
57
79
|
],
|
|
58
80
|
"publishConfig": {
|
|
59
81
|
"access": "public"
|