@eventra_dev/eventra-cli 2.1.0 → 2.2.0
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 +16 -6
- package/dist/analysis/symbols/wrapperRegistry.js +14 -5
- package/dist/cli/check.js +25 -9
- package/dist/cli/init.js +45 -16
- package/dist/cli/send.js +31 -2
- package/dist/cli/sync.js +13 -10
- package/dist/cli/watch.js +163 -62
- package/dist/compiler/compilerContext.js +103 -5
- package/dist/compiler/documentRegistry.js +15 -1
- package/dist/compiler/importGraph.js +8 -0
- package/dist/config/config.js +45 -2
- package/dist/core/EventraEngine.js +129 -16
- package/dist/core/catalogMeta.js +91 -0
- package/dist/core/fileLimits.js +12 -0
- package/dist/core/projectScanner.js +14 -24
- package/dist/core/scanFilters.js +146 -0
- package/dist/core/scanResults.js +21 -7
- package/dist/core/skipped.js +77 -0
- package/dist/index.js +8 -5
- package/dist/plugin/builtins/consoleDynamicEventReporter.js +18 -2
- package/dist/plugin/loadPlugins.js +20 -2
- package/dist/plugin/registry.js +50 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
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
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
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-
|
|
11
|
+
<img alt="unit tests" src="https://img.shields.io/badge/unit-783%20passing-brightgreen?style=flat-square&logo=vitest&logoColor=white">
|
|
12
12
|
<img alt="e2e fixtures" src="https://img.shields.io/badge/e2e-14%20fixtures-brightgreen?style=flat-square">
|
|
13
13
|
<img alt="coverage" src="https://img.shields.io/badge/coverage-100%25%20(unit)-brightgreen?style=flat-square&logo=vitest&logoColor=white">
|
|
14
14
|
<img alt="node" src="https://img.shields.io/node/v/@eventra_dev/eventra-cli?style=flat-square&color=darkgreen&logo=node.js&logoColor=white">
|
|
@@ -212,7 +212,7 @@ Everything found is pre-selected; press Enter to accept or untick what you do no
|
|
|
212
212
|
| `amplitude` | `@amplitude/analytics-browser`, `@amplitude/analytics-node`, `@amplitude/analytics-react-native`, `@amplitude/unified`, `@amplitude/marketing-analytics-browser`, and the older `amplitude-js` |
|
|
213
213
|
| `ga4` | `react-ga4`, `vue-gtag`, `@next/third-parties`, `nuxt-gtag`, `@types/gtag.js`, and the older `vue-gtag-next` |
|
|
214
214
|
|
|
215
|
-
Detection reads dependencies only. A tag pasted straight into the HTML (a Segment or `gtag.js` snippet with no package) is not seen: add the preset to `sinks` yourself. In a monorepo only the block-list form of `packages:` in `pnpm-workspace.yaml` is understood (not `packages: [a, b]`), and workspace patterns that are absolute or contain `..` are ignored.
|
|
215
|
+
Detection reads dependencies only. A tag pasted straight into the HTML (a Segment or `gtag.js` snippet with no package) is not seen: add the preset to `sinks` yourself. In a monorepo only the block-list form of `packages:` in `pnpm-workspace.yaml` is understood (not `packages: [a, b]`), and workspace patterns that are absolute or contain `..` are ignored. If `eventra.json` already exists, `init` asks before overwriting it (the default answer is no). `eventra init --yes` never prompts: it uses the default endpoint, accepts every detected preset, and leaves an existing `eventra.json` untouched, so it is safe in scripts and CI.
|
|
216
216
|
|
|
217
217
|
### `eventra sync`
|
|
218
218
|
|
|
@@ -221,9 +221,11 @@ Full project scan → updates `eventra.json`:
|
|
|
221
221
|
- `events` — discovered event names
|
|
222
222
|
- `functionWrappers` — detected wrapper functions
|
|
223
223
|
|
|
224
|
+
A file that cannot be analysed (a broken `.vue`, `.svelte`, `.astro` or `.html`, an unreadable file, or one over 2 MB) is skipped, and `sync` prints one summary at the end listing each skipped file and why. While anything is skipped, events already in `eventra.json` are kept, because a missing event may simply live in a skipped file. The exit code stays `0`; `eventra sync --strict` exits `1` if any file was skipped.
|
|
225
|
+
|
|
224
226
|
### `eventra check`
|
|
225
227
|
|
|
226
|
-
Compares config with the current scan (events + wrappers). Exit code `1` on drift.
|
|
228
|
+
Compares config with the current scan (events + wrappers). Exit code `1` on drift. When files were skipped, known events that were not found are not reported as removed (they cannot be verified) and the skipped files are listed; `eventra check --strict` also exits `1` if any file was skipped.
|
|
227
229
|
|
|
228
230
|
### `eventra check --fix`
|
|
229
231
|
|
|
@@ -255,6 +257,14 @@ This records the endpoint in `eventra.local.json` (gitignored). If `eventra.json
|
|
|
255
257
|
|
|
256
258
|
Once `eventra.json` already commits an `endpoint`, that committed value is what events actually get sent to - `EVENTRA_ENDPOINT` only controls whether the approval prompt is skipped; its own value is not read or compared against anything in that case. `EVENTRA_ENDPOINT` becomes the real destination only when `eventra.json` has no `endpoint` set at all. In other words, setting the variable to point somewhere else does not redirect traffic away from an already-committed endpoint - it only means you won't be asked to approve it.
|
|
257
259
|
|
|
260
|
+
**Catalog snapshot.** Besides the event list, `send` scans the project once more and tells the server how trustworthy that list is, so the dashboard can compare what is in your code with what actually arrived. It adds four optional fields to the request: `complete`, `dynamicCount`, `branch` and `cliVersion`.
|
|
261
|
+
|
|
262
|
+
- `complete` is `true` only when the scan skipped no file, hit no error, and `eventra.json` matches what the scan just found. The server marks names that are missing from a complete snapshot as removed from code, so anything doubtful is `false` (a file that could not be parsed, a failed scan, or an `eventra.json` that is out of date: run `eventra sync` first). A request that is not complete never removes anything.
|
|
263
|
+
- `dynamicCount` is the number of event-name call sites that could not be resolved to any string (for example `track(name)` with an unknown `name`). The dashboard uses it to warn that such names may show up as unexpected events.
|
|
264
|
+
- `branch` is the current branch, read from the usual CI variables (`GITHUB_REF_NAME`, `CI_COMMIT_REF_NAME`, `BRANCH_NAME`, `BITBUCKET_BRANCH`, `CIRCLE_BRANCH`, `BUILDKITE_BRANCH`, `VERCEL_GIT_COMMIT_REF`) and otherwise from `git`; it is left out when unknown (for example a detached checkout with no CI variable). `cliVersion` is the CLI version.
|
|
265
|
+
|
|
266
|
+
Run `send` from the main branch in CI: a snapshot from a feature branch describes that branch. A server that predates these fields rejects them, so `send` repeats the request once with the plain event list. The extra scan adds the same time as one `eventra sync`.
|
|
267
|
+
|
|
258
268
|
Network resilience:
|
|
259
269
|
|
|
260
270
|
- Up to 4 attempts with exponential backoff + jitter (capped at 8 s)
|
|
@@ -416,7 +426,7 @@ No dependency on `@eventra_dev/eventra-cli` is required — the host CLI loads t
|
|
|
416
426
|
| `plugins` | `@eventra_dev/cli-plugin-*` specifiers to `import()` at startup. Anything outside that scope is skipped (see [Plugin contract](#plugin-contract-for-authors)) |
|
|
417
427
|
| `sinks` | Third-party analytics calls to treat as event sources, e.g. `[{ "preset": "posthog" }]` (see [Third-party analytics](#third-party-analytics-sinks)) |
|
|
418
428
|
| `sync.include` | Base glob patterns; plugin `includeGlobs` are merged automatically |
|
|
419
|
-
| `sync.exclude` | Paths skipped during scan |
|
|
429
|
+
| `sync.exclude` | Paths skipped during scan. A plain name such as `node_modules` or `dist` matches at any depth (nested `packages/*/node_modules` too); entries containing `/` or glob characters are used as written |
|
|
420
430
|
|
|
421
431
|
`eventra init`/`eventra send` never write a real key into this file — they write to `eventra.local.json` instead, which is automatically added to `.gitignore`.
|
|
422
432
|
|
|
@@ -440,14 +450,14 @@ No runtime execution. No monkey-patching.
|
|
|
440
450
|
|
|
441
451
|
## Requirements
|
|
442
452
|
|
|
443
|
-
- Node.js 18+
|
|
453
|
+
- Node.js 18+ (`eventra watch` needs Node.js 20.19+, which its file-watching dependency requires; `init`, `sync`, `check` and `send` run on 18)
|
|
444
454
|
- TypeScript/JavaScript source using `@eventra_dev/eventra-sdk`
|
|
445
455
|
|
|
446
456
|
---
|
|
447
457
|
|
|
448
458
|
## Test Coverage
|
|
449
459
|
|
|
450
|
-
Two test layers, **
|
|
460
|
+
Two test layers, **783 unit tests + 14 e2e fixtures + 3 `check` exit-code scenarios + 1 `watch` scenario**.
|
|
451
461
|
|
|
452
462
|
**100% statement/branch/function/line coverage** (v8 provider, `pnpm test:coverage`), enforced via a `coverage.thresholds` block in `vitest.config.ts` so a regression fails the build instead of silently slipping under 100%. This measures the vitest unit layer only; the e2e/`check`/`watch` fixtures run against the compiled `dist/` binary via `tsx tests/run.ts` and aren't coverage-instrumented. A handful of genuinely unreachable defensive branches (TS-internal invariants — e.g. an aliased import symbol is always fully resolved before its own declarations are ever inspected — that don't hold a second outcome in practice) are marked with `/* v8 ignore */`, with an inline comment explaining why, rather than tested.
|
|
453
463
|
|
|
@@ -7,18 +7,27 @@ function isSymbol(value) {
|
|
|
7
7
|
return typeof value.getDeclarations === "function";
|
|
8
8
|
}
|
|
9
9
|
class WrapperRegistry {
|
|
10
|
-
|
|
10
|
+
pendingChecker;
|
|
11
11
|
exportCache;
|
|
12
12
|
wrappers = new WeakMap();
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
resolvedChecker;
|
|
14
|
+
/** `checker` may be a provider, called once on first use (setChecker replaces it). */
|
|
15
|
+
constructor(pendingChecker, exportCache) {
|
|
16
|
+
this.pendingChecker = pendingChecker;
|
|
15
17
|
this.exportCache = exportCache;
|
|
16
18
|
}
|
|
17
19
|
setChecker(checker) {
|
|
18
|
-
this.
|
|
20
|
+
this.resolvedChecker = checker;
|
|
19
21
|
}
|
|
20
22
|
getChecker() {
|
|
21
|
-
|
|
23
|
+
if (!this.resolvedChecker) {
|
|
24
|
+
this.resolvedChecker =
|
|
25
|
+
typeof this.pendingChecker === "function" ? this.pendingChecker() : this.pendingChecker;
|
|
26
|
+
}
|
|
27
|
+
return this.resolvedChecker;
|
|
28
|
+
}
|
|
29
|
+
get checker() {
|
|
30
|
+
return this.getChecker();
|
|
22
31
|
}
|
|
23
32
|
normalize(symbol) {
|
|
24
33
|
return ((0, exportResolver_1.resolveExportedSymbol)(symbol, this.checker, this.exportCache) ?? symbol);
|
package/dist/cli/check.js
CHANGED
|
@@ -8,30 +8,41 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_1 = require("../config/config");
|
|
9
9
|
const projectScanner_1 = require("../core/projectScanner");
|
|
10
10
|
const scanResults_1 = require("../core/scanResults");
|
|
11
|
+
const skipped_1 = require("../core/skipped");
|
|
11
12
|
function diffSets(found, known) {
|
|
12
13
|
return {
|
|
13
14
|
added: [...found].filter((e) => !known.has(e)),
|
|
14
15
|
removed: [...known].filter((e) => !found.has(e)),
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
|
-
async function check({ fix = false }) {
|
|
18
|
-
const config = await (0, config_1.
|
|
19
|
-
if (!config)
|
|
20
|
-
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
18
|
+
async function check({ fix = false, strict = false } = {}) {
|
|
19
|
+
const config = await (0, config_1.requireConfig)();
|
|
20
|
+
if (!config)
|
|
21
|
+
return;
|
|
23
22
|
console.log(chalk_1.default.blue("Checking events and function wrappers..."));
|
|
24
|
-
const { engine, plugins } = await (0, projectScanner_1.scanProject)(config);
|
|
23
|
+
const { engine, plugins, skipped, ignored } = await (0, projectScanner_1.scanProject)(config);
|
|
25
24
|
await plugins.runDynamicEventReporters(engine.getAllDynamicOccurrences());
|
|
25
|
+
const partial = skipped.length > 0;
|
|
26
26
|
const foundEvents = new Set(engine.getAllEvents());
|
|
27
27
|
const knownEvents = new Set(config.events ?? []);
|
|
28
28
|
const eventDiff = diffSets(foundEvents, knownEvents);
|
|
29
29
|
const foundWrappers = new Set(engine.getAllFunctionWrappers());
|
|
30
30
|
const knownWrappers = new Set(config.functionWrappers?.map((w) => w.name) ?? []);
|
|
31
31
|
const wrapperDiff = diffSets(foundWrappers, knownWrappers);
|
|
32
|
+
// With skipped files the scan is incomplete: a known event that was not found
|
|
33
|
+
// may simply live in a skipped file, so it is not reported as removed.
|
|
34
|
+
const unverified = partial ? eventDiff.removed.length + wrapperDiff.removed.length : 0;
|
|
35
|
+
if (partial) {
|
|
36
|
+
eventDiff.removed = [];
|
|
37
|
+
wrapperDiff.removed = [];
|
|
38
|
+
}
|
|
32
39
|
if (fix) {
|
|
33
|
-
await (0, config_1.saveConfig)((0, scanResults_1.buildConfigFromScan)(config, engine));
|
|
40
|
+
await (0, config_1.saveConfig)((0, scanResults_1.buildConfigFromScan)(config, engine, { preserveKnown: partial }));
|
|
34
41
|
console.log(chalk_1.default.green("Synced"));
|
|
42
|
+
(0, skipped_1.reportSkipped)(skipped);
|
|
43
|
+
(0, skipped_1.reportIgnored)(ignored);
|
|
44
|
+
if (strict && partial)
|
|
45
|
+
process.exitCode = 1;
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
37
48
|
let hasDiff = false;
|
|
@@ -47,7 +58,12 @@ async function check({ fix = false }) {
|
|
|
47
58
|
wrapperDiff.added.forEach((w) => console.log(chalk_1.default.red(`+ ${w}`)));
|
|
48
59
|
wrapperDiff.removed.forEach((w) => console.log(chalk_1.default.yellow(`- ${w}`)));
|
|
49
60
|
}
|
|
50
|
-
|
|
61
|
+
(0, skipped_1.reportSkipped)(skipped);
|
|
62
|
+
(0, skipped_1.reportIgnored)(ignored);
|
|
63
|
+
if (unverified > 0) {
|
|
64
|
+
console.error(chalk_1.default.gray(`${(0, skipped_1.plural)(unverified, "known entry", "known entries")} could not be verified because files were skipped.`));
|
|
65
|
+
}
|
|
66
|
+
if (hasDiff || (strict && partial)) {
|
|
51
67
|
process.exit(1);
|
|
52
68
|
}
|
|
53
69
|
console.log(chalk_1.default.green("All good"));
|
package/dist/cli/init.js
CHANGED
|
@@ -5,7 +5,9 @@ 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"));
|
|
8
9
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
9
11
|
const config_1 = require("../config/config");
|
|
10
12
|
const sinkPresets_1 = require("../plugin/builtins/sinkPresets");
|
|
11
13
|
const projectDependencies_1 = require("./projectDependencies");
|
|
@@ -13,11 +15,13 @@ const projectDependencies_1 = require("./projectDependencies");
|
|
|
13
15
|
* Looks for analytics libraries in package.json (and workspace members) and, if there are any,
|
|
14
16
|
* asks which of them Eventra should read events from. Everything detected is pre-selected.
|
|
15
17
|
*/
|
|
16
|
-
async function chooseSinks() {
|
|
18
|
+
async function chooseSinks(yes) {
|
|
17
19
|
const detected = (0, sinkPresets_1.detectSinkPresets)(await (0, projectDependencies_1.collectDependencyNames)(process.cwd()));
|
|
18
20
|
if (detected.length === 0)
|
|
19
21
|
return [];
|
|
20
22
|
console.log(chalk_1.default.gray(`Found analytics in package.json: ${detected.map((d) => d.packages.join(", ")).join("; ")}`));
|
|
23
|
+
if (yes)
|
|
24
|
+
return detected.map((d) => ({ preset: d.preset }));
|
|
21
25
|
const { presets } = await inquirer_1.default.prompt([
|
|
22
26
|
{
|
|
23
27
|
type: "checkbox",
|
|
@@ -32,22 +36,47 @@ async function chooseSinks() {
|
|
|
32
36
|
]);
|
|
33
37
|
return presets.map((preset) => ({ preset }));
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
const DEFAULT_ENDPOINT = "https://api.eventra.dev/api/v1/cli/events";
|
|
40
|
+
/**
|
|
41
|
+
* `yes` is the non-interactive mode (CI, scripts): defaults for every answer,
|
|
42
|
+
* and an existing eventra.json is never overwritten.
|
|
43
|
+
*/
|
|
44
|
+
async function init({ yes = false } = {}) {
|
|
45
|
+
if (await fs_extra_1.default.pathExists(path_1.default.join(process.cwd(), config_1.CONFIG_NAME))) {
|
|
46
|
+
if (yes) {
|
|
47
|
+
console.log(chalk_1.default.yellow(`${config_1.CONFIG_NAME} already exists, keeping it`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const { overwrite } = await inquirer_1.default.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: "confirm",
|
|
53
|
+
name: "overwrite",
|
|
54
|
+
message: `${config_1.CONFIG_NAME} already exists. Overwrite it? Its events, wrappers, plugins and sinks will be lost.`,
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
if (!overwrite) {
|
|
59
|
+
console.log(chalk_1.default.gray(`Kept the existing ${config_1.CONFIG_NAME}`));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
36
63
|
console.log(chalk_1.default.blue("Initializing Eventra..."));
|
|
37
|
-
const { apiKey, endpoint, } =
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
const { apiKey, endpoint, } = yes
|
|
65
|
+
? { apiKey: "", endpoint: DEFAULT_ENDPOINT }
|
|
66
|
+
: await inquirer_1.default.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: "input",
|
|
69
|
+
name: "apiKey",
|
|
70
|
+
message: "API Key (optional):",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "input",
|
|
74
|
+
name: "endpoint",
|
|
75
|
+
message: "Custom endpoint (optional):",
|
|
76
|
+
default: DEFAULT_ENDPOINT,
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
const sinks = await chooseSinks(yes);
|
|
51
80
|
console.log("");
|
|
52
81
|
console.log(chalk_1.default.gray("Eventra detects @eventra_dev/eventra-sdk usage:"));
|
|
53
82
|
console.log(chalk_1.default.gray('• tracker.track("event.name")'));
|
package/dist/cli/send.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.send = send;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
9
|
const config_1 = require("../config/config");
|
|
10
|
+
const catalogMeta_1 = require("../core/catalogMeta");
|
|
10
11
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
11
12
|
const CLI_VERSION = package_json_1.default.version;
|
|
12
13
|
// Always trusted, no local approval needed — matches `eventra init`'s prompt default.
|
|
@@ -64,6 +65,10 @@ async function attemptDelivery(endpoint, apiKey, payload) {
|
|
|
64
65
|
clearTimeout(timeout);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
// the API answers a rejected unknown property with {"error":{"code":"VALIDATION_ERROR",...}}
|
|
69
|
+
function isValidationError(body) {
|
|
70
|
+
return typeof body === "string" && body.includes("VALIDATION_ERROR");
|
|
71
|
+
}
|
|
67
72
|
async function send(opts = {}) {
|
|
68
73
|
const config = await (0, config_1.loadConfig)();
|
|
69
74
|
if (!config) {
|
|
@@ -134,24 +139,48 @@ async function send(opts = {}) {
|
|
|
134
139
|
}
|
|
135
140
|
console.log("");
|
|
136
141
|
console.log(chalk_1.default.blue(`Sending ${config.events.length} events...`));
|
|
137
|
-
|
|
142
|
+
// Tells the server whether this list is a full snapshot of the code (so names
|
|
143
|
+
// missing from it can be marked as removed) and how many event names could
|
|
144
|
+
// not be resolved. Older servers ignore the extra fields.
|
|
145
|
+
const meta = await (0, catalogMeta_1.collectCatalogMeta)(config);
|
|
146
|
+
if (!meta.complete) {
|
|
147
|
+
console.log(chalk_1.default.gray("Catalog snapshot marked incomplete (files were skipped, the scan failed, or eventra.json is out of date: run 'eventra sync')"));
|
|
148
|
+
}
|
|
149
|
+
const legacyBody = {
|
|
138
150
|
events: config.events,
|
|
139
151
|
cli: {
|
|
140
152
|
name: "@eventra_dev/eventra-cli",
|
|
141
153
|
version: CLI_VERSION,
|
|
142
154
|
runtime: "node",
|
|
143
155
|
},
|
|
156
|
+
};
|
|
157
|
+
const payload = JSON.stringify({
|
|
158
|
+
...legacyBody,
|
|
159
|
+
complete: meta.complete,
|
|
160
|
+
cliVersion: CLI_VERSION,
|
|
161
|
+
...(meta.dynamicCount !== undefined ? { dynamicCount: meta.dynamicCount } : {}),
|
|
162
|
+
...(meta.branch ? { branch: meta.branch } : {}),
|
|
144
163
|
});
|
|
164
|
+
// A server that predates the catalog fields rejects unknown properties with a
|
|
165
|
+
// generic validation 400; the request is then repeated once without them.
|
|
166
|
+
const legacyPayload = JSON.stringify(legacyBody);
|
|
167
|
+
let body = payload;
|
|
145
168
|
// attempt delivery with retry on 429/5xx/network errors
|
|
146
169
|
let data;
|
|
147
170
|
let lastFailure = null;
|
|
148
171
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
149
172
|
try {
|
|
150
|
-
const outcome = await attemptDelivery(endpoint, apiKey,
|
|
173
|
+
const outcome = await attemptDelivery(endpoint, apiKey, body);
|
|
151
174
|
if (outcome.ok) {
|
|
152
175
|
data = outcome.data;
|
|
153
176
|
break;
|
|
154
177
|
}
|
|
178
|
+
if (outcome.status === 400 && body !== legacyPayload && isValidationError(outcome.body)) {
|
|
179
|
+
body = legacyPayload;
|
|
180
|
+
console.log(chalk_1.default.gray("Server does not know the catalog fields yet, sending the plain event list"));
|
|
181
|
+
attempt--;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
155
184
|
if (!outcome.retryable) {
|
|
156
185
|
// permanent failure — surface immediately
|
|
157
186
|
console.log(chalk_1.default.red(`Request failed (${outcome.status})`));
|
package/dist/cli/sync.js
CHANGED
|
@@ -8,17 +8,20 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const config_1 = require("../config/config");
|
|
9
9
|
const projectScanner_1 = require("../core/projectScanner");
|
|
10
10
|
const scanResults_1 = require("../core/scanResults");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
const skipped_1 = require("../core/skipped");
|
|
12
|
+
async function sync({ strict = false } = {}) {
|
|
13
|
+
const config = await (0, config_1.requireConfig)();
|
|
14
|
+
if (!config)
|
|
15
15
|
return;
|
|
16
|
-
}
|
|
17
16
|
console.log(chalk_1.default.blue("Scanning..."));
|
|
18
|
-
const { engine, plugins } = await (0, projectScanner_1.scanProject)(config);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
console.log(chalk_1.default.green(`Found ${events
|
|
17
|
+
const { engine, plugins, skipped, ignored } = await (0, projectScanner_1.scanProject)(config);
|
|
18
|
+
// report what was saved: with skipped files that is the union of the known and
|
|
19
|
+
// the found events, not just what this scan found
|
|
20
|
+
const saved = await (0, scanResults_1.persistScanResults)(config, engine, { preserveKnown: skipped.length > 0 });
|
|
21
|
+
console.log(chalk_1.default.green(`Found ${(0, skipped_1.plural)(saved.events?.length ?? 0, "event")}, ${(0, skipped_1.plural)(saved.functionWrappers?.length ?? 0, "function wrapper")}`));
|
|
23
22
|
await plugins.runDynamicEventReporters(engine.getAllDynamicOccurrences());
|
|
23
|
+
(0, skipped_1.reportSkipped)(skipped);
|
|
24
|
+
(0, skipped_1.reportIgnored)(ignored);
|
|
25
|
+
if (strict && skipped.length > 0)
|
|
26
|
+
process.exitCode = 1;
|
|
24
27
|
}
|