@eventra_dev/eventra-cli
v1.0.2
Published
Static analytics event extraction CLI powered by the TypeScript compiler API
Maintainers
Keywords
Readme
Eventra CLI
Eventra CLI statically discovers analytics events from @eventra_dev/eventra-sdk usage in your codebase — including function wrappers and cross-file propagation chains.
Overview
The CLI scans TypeScript and JavaScript with the TypeScript compiler API and extracts only calls to Eventra.prototype.track() on instances of Eventra imported from @eventra_dev/eventra-sdk.
It does not detect generic track(), Segment, Google Analytics, or other libraries out of the box. Framework files (e.g. .vue) require an optional plugin.
Installation
npm install -D @eventra_dev/eventra-cli
# or
pnpm add -D @eventra_dev/eventra-cliYour project should use the runtime SDK:
pnpm add @eventra_dev/eventra-sdkQuick Start
eventra init
eventra sync
eventra check
eventra sendWhat gets detected
Direct SDK calls
import { Eventra } from "@eventra_dev/eventra-sdk";
const tracker = new Eventra({ apiKey: "YOUR_PROJECT_API_KEY" });
tracker.track("checkout.completed");
tracker.track("app.loaded", { userId: "user_123" });
tracker?.track("optional.chain");Function wrappers (propagation)
Wrappers that call Eventra.track() inside are registered automatically:
function trackFeature(name: string) {
sdk.track(name);
}
trackFeature("purchase");Variables, templates, conditionals
const EVENT = "signup";
tracker.track(EVENT);
tracker.track(`feature_${type}`);
tracker.track(flag ? "path.a" : "path.b");Cross-file
// tracker.ts
export function trackFeature(name: string) {
client.track(name);
}
// app.ts
import { trackFeature } from "./tracker";
trackFeature("purchase");Event name rules (aligned with SDK)
| Rule | Limit |
|------|--------|
| Max length | 64 characters (same as SDK) |
| Allowed characters | a-zA-Z0-9:_./- |
| Position | First argument of .track(name, options?) |
The second argument (userId, properties) is not used as the event name.
What is ignored
// Not from @eventra_dev/eventra-sdk
track("legacy");
analytics.track("ga");
segment.track("x");
// Wrong API shape for SDK (object as first argument)
tracker.track({ event: "click" });Commands
eventra sync
Full project scan → updates eventra.json:
events— discovered event namesfunctionWrappers— detected wrapper functions
eventra check
Compares config with the current scan (events + wrappers). Exit code 1 on drift.
eventra check --fix
Writes scan results into eventra.json.
eventra watch
Incremental scan with the same rules as sync.
eventra send
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.
Network resilience:
- Up to 4 attempts with exponential backoff + jitter (capped at 8 s)
- Retries on 429 and 5xx responses, and on transport errors (timeout / DNS / connection reset)
- Permanent failure (4xx other than 429) surfaces immediately without retry
- 10 s timeout per attempt via
AbortController
Plugins
The CLI core is framework-agnostic. Extensions are separate npm packages with their own types — the CLI loads them from eventra.json and adapts their output internally.
Vue (.vue SFC)
Install the official Vue plugin and enable it in config:
pnpm add -D @eventra_dev/cli-plugin-vue{
"plugins": ["@eventra_dev/cli-plugin-vue"],
"sync": {
"include": ["**/*.{ts,tsx,js,jsx}"],
"exclude": ["node_modules", "dist", ".next", ".git"]
}
}The plugin:
- splits each
.vuefile into virtual TypeScript modules (App.vue.ts,App.vue.template.ts) - extracts
track()calls from<script>blocks - detects static
event="feature_name"attributes in<template>(literals only)
sync.include does not need **/*.vue manually — the plugin registers **/*.vue via includeGlobs.
Before publishing / local development
Plugins are resolved from your project's node_modules (same as any dependency):
{
"devDependencies": {
"@eventra_dev/cli-plugin-vue": "file:../cli-plugin-vue"
}
}The plugin package must be built (dist/) before use. Unpublished plugins work the same way — only the install source differs.
Plugin contract (for authors)
External plugins export an object (or factory) with:
| Field | Purpose |
|-------|---------|
| id | Unique preprocessor name |
| includeGlobs | Extra glob patterns merged into the scan |
| match(path) | Whether this plugin handles a file |
| transform({ path, source }) | Returns { modules: [{ path, content }] } |
| staticSinks? | Declarative callee-based sink rules (CLI converts to internal detectors) |
No dependency on @eventra_dev/eventra-cli is required. See ARCHITECTURE.md for details.
Configuration
{
"apiKey": "",
"endpoint": "",
"events": [],
"functionWrappers": [],
"plugins": [],
"sync": {
"include": ["**/*.{ts,tsx,js,jsx}"],
"exclude": ["node_modules", "dist", ".next", ".git"]
}
}| Field | Description |
|-------|-------------|
| plugins | Module specifiers to import() at startup (e.g. @eventra_dev/cli-plugin-vue) |
| sync.include | Base glob patterns; plugin includeGlobs are merged automatically |
| sync.exclude | Paths skipped during scan |
How it works
- Load built-in plugins (
eventra-sdksink detector) and any packages listed inplugins. - Glob project files (
sync.include+ pluginincludeGlobs). - Run file preprocessors (e.g.
.vue→ virtual.tsmodules). - Load sources into an incremental TypeScript program (with SDK type shim).
- Phase 1 — find
Eventrainstances from@eventra_dev/eventra-sdkand register function wrappers that call.track(). - Phase 2 — resolve static event names and wrapper propagation chains (sink detector chain includes plugin sinks).
- Write results to
eventra.json.
watch tracks disk source files (including .vue), re-runs preprocessors on change, and incrementally updates the engine.
No runtime execution. No monkey-patching.
Requirements
- Node.js 18+
- TypeScript/JavaScript source using
@eventra_dev/eventra-sdk
Test Coverage
Two test layers, 59 unit tests + 12 e2e fixtures + 3 check exit-code scenarios.
Unit tests (vitest) — 13 suites covering core modules:
| Module | Covers |
|---|---|
| ImportGraph | Forward/reverse edges, cycles, stale-edge cleanup, file removal |
| Scheduler | Batch coalescing, last-write-wins per file, sequential bursts, error propagation |
| DocumentRegistry | Path normalization, version bumping, no-op on identical content, ensure() from disk |
| CompilerContext | Stage/update/remove files, resolveModule with tsconfig.json paths, source-file enumeration |
| EventraEngine | Direct calls, SDK isolation, cross-file wrappers, file updates, file removal, wrapper filtering |
| PluginRegistry | Built-in SDK sink, preprocessors, virtual-path mapping, include-pattern dedup |
| external plugin adapter | Transform output mapping, static sink registration, invalid result rejection |
| vue-shaped external plugin | Adapter path for script + template virtual modules and staticSinks |
| processFile | Script-kind detection, import/export specifier extraction |
| extractTemplateExpressions | Vue/Svelte/Astro attribute patterns |
| normalizeConfig | Sort events, dedupe wrappers, defaults, preserve apiKey / endpoint / sync / plugins |
| buildConfigFromScan | Replace events + wrappers, preserve everything else |
| hash | Stability and uniqueness |
End-to-end fixtures — 12 isolated TS projects scanned via eventra sync:
| Fixture | Covers |
|---|---|
| sdk/direct | Plain tracker.track() calls |
| frontend/react, frontend/next, frontend/vue | Framework-specific code shapes |
| backend/node, backend/express, backend/nest | Backend wrappers and middleware chains |
| wrappers/function | Local wrapper functions, methods, objects, ternaries, templates |
| wrappers/barrel | export * from "./tracker" re-exports |
| wrappers/default-export | export default function trackFeature propagation |
| wrappers/path-aliases | tsconfig.json paths mapping (@app/*) |
| watch-incremental | Engine state across sequential file updates (matches sync output) |
eventra check exit-code scenarios:
- Drift → exit
1 --fixwrites scan results intoeventra.json→ exit0- Parity (no drift) → exit
0
Run locally:
pnpm --filter @eventra_dev/eventra-cli test # unit + e2e + exit codes
pnpm --filter @eventra_dev/eventra-cli test:unit # vitest only
pnpm --filter @eventra_dev/eventra-cli test:e2e # fixtures + check exit codesLicense
MIT
