autotel-eventcatalog 26.0.0 → 27.0.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/CHANGELOG.md +38 -0
- package/README.md +105 -2
- package/dist/cli.cjs +144 -36
- package/dist/cli.js +126 -19
- package/dist/index.cjs +41 -35
- package/dist/index.d.cts +117 -2
- package/dist/index.d.ts +117 -2
- package/dist/index.js +2 -2
- package/dist/map-html-AxfF9V_g.cjs +2042 -0
- package/dist/map-html-Dm_gpZUi.js +1757 -0
- package/package.json +8 -6
- package/dist/generate-C1vYn-_M.cjs +0 -1480
- package/dist/generate-w_py2l3I.js +0 -1237
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 27.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3926832: Draw the event catalog by what the runtime actually did, and bridge Effect logs.
|
|
8
|
+
|
|
9
|
+
**`autotel-eventcatalog`** gains a `map` command that renders the catalog topology as one self-contained HTML file, with every edge labelled by the evidence behind it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
autotel-eventcatalog map --snapshot snap.json --catalog ./catalog --output map.html
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- **observed** — a real `track()` call crossed it, stroke weight scaled to volume
|
|
16
|
+
- **declared, never seen** — the catalog says it happens and this run never saw it
|
|
17
|
+
- **ran, not in the catalog** — it happened and nobody wrote it down
|
|
18
|
+
- **consumer asserted** — the event fired; its delivery is a claim from the catalog
|
|
19
|
+
|
|
20
|
+
Three modes: `static` (commit it and read it in a PR), `replay` (markers move at a rate drawn from observed counts), and `live` (`--live-url`, and a marker crosses an edge the moment that event fires). Motion is reserved for evidence, so a declared-but-never-seen edge stays still in every mode.
|
|
21
|
+
|
|
22
|
+
The map and the drift report pair a rename together as one finding, while still listing both sides. `buildLiveMap()` and `renderLiveMapHtml()` are exported for building your own view, and `normaliseEventId()` is now public so callers can match dotted `track()` names to PascalCase catalog ids the same way the drift report does. The live page is covered by browser tests (`pnpm test:browser`) driving the real artifact in Chromium.
|
|
23
|
+
|
|
24
|
+
**`autotel-subscribers`**: `ArchitectureSnapshotSubscriber` now records `sources` on each observation — one entry per distinct `(producer, channel)` pair, with that pair's own count and first/last-seen — so two services publishing the same event name each keep their own traffic. Additive and sorted, so existing readers are unaffected and a committed snapshot stays byte-stable.
|
|
25
|
+
|
|
26
|
+
**`autotel-effect`**: `layer()` now bridges logs as well as spans, from the same single call.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const AutotelEffect = layer({ serviceName: 'my-api' }); // spans + logs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`Effect.log*` is emitted as an OpenTelemetry log record — reaching any OTLP log backend, autotel-devtools included — plus a trace-correlated structured line on stdout, so a log written inside `Effect.withSpan` carries that span's trace and span ids. `Effect.annotateLogs` values become attributes, and an `Error` or `Cause` is reported as `err` with its stack.
|
|
33
|
+
|
|
34
|
+
**Breaking:** `Effect.log*` previously went to Effect's console logger and stayed there. Pass `logs: false` to keep `layer()` bridging spans only, or an options object (`level`, `pretty`, `mergeWithExisting`, `console`) to configure the logger. `loggerLayer()` is exported for the case where something else owns the tracer.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [3926832]
|
|
39
|
+
- autotel-subscribers@56.1.0
|
|
40
|
+
|
|
3
41
|
## 26.0.0
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -13,13 +13,14 @@ the code actually does at runtime.
|
|
|
13
13
|
> on what channel. This package reads that snapshot and compares it to
|
|
14
14
|
> your catalog.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Four tools:
|
|
17
17
|
|
|
18
18
|
| Command | Mode | What it does |
|
|
19
19
|
| -------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
20
|
| **`drift`** | read-only | Diffs the catalog against a snapshot. Reports findings as Markdown, JSON, or plain text. The PR check that catches "you added an event but forgot to document it." |
|
|
21
21
|
| **`generate`** | write | Scaffolds EventCatalog resources from a snapshot: services, events, channels, inferred JSON Schemas, and producer↔event↔channel relationships. |
|
|
22
22
|
| **`stamp`** | write | Writes a runtime evidence block (counts, last-seen, field paths) into each event's `index.mdx` between idempotent markers. Keeps the static catalog page reflecting production behaviour. |
|
|
23
|
+
| **`map`** | write | Renders the catalog topology as one self-contained HTML file where every arrow says whether the runtime actually crossed it. Static, replay, or live. |
|
|
23
24
|
|
|
24
25
|
Both share inputs: an autotel snapshot JSON file and an EventCatalog
|
|
25
26
|
directory. Both ship a versioned JSON summary you can gate CI on. The
|
|
@@ -34,7 +35,9 @@ To keep the scope tight:
|
|
|
34
35
|
[`autotel-subscribers`'s `ArchitectureSnapshotSubscriber`](../autotel-subscribers).
|
|
35
36
|
This package only consumes them.
|
|
36
37
|
- **Does not run any web server or dashboard.** Live dashboards live in
|
|
37
|
-
example apps. This package is a CLI plus library plus action.
|
|
38
|
+
example apps. This package is a CLI plus library plus action. `map --mode live`
|
|
39
|
+
emits a page that _connects to_ an SSE endpoint you already serve; it does not
|
|
40
|
+
serve one.
|
|
38
41
|
- **Does not infer drift contracts from payload samples during `drift`.**
|
|
39
42
|
Field-path drift is set-difference on dotted paths. Type/value drift is
|
|
40
43
|
checked only against declared schema constraints. (`generate` can scaffold
|
|
@@ -44,6 +47,106 @@ To keep the scope tight:
|
|
|
44
47
|
the `stamp` command writes is between `<!-- autotel:stamp-start -->`
|
|
45
48
|
and `<!-- autotel:stamp-end -->`. Outside those markers is yours.
|
|
46
49
|
|
|
50
|
+
## `map`: the diagram that knows what ran
|
|
51
|
+
|
|
52
|
+
A catalog diagram draws every arrow the same way. A relationship four teams
|
|
53
|
+
believe in and one that has not carried a message since March look identical,
|
|
54
|
+
and no amount of reading the picture tells you which is which.
|
|
55
|
+
|
|
56
|
+
`map` renders the same topology with the snapshot joined in:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
autotel-eventcatalog map \
|
|
60
|
+
--snapshot ./services/test/snapshot.json \
|
|
61
|
+
--catalog ./catalog \
|
|
62
|
+
--output ./catalog-map.html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
13 observed, 2 declared-but-never-seen, 2 undocumented, 5 asserted (mode: static)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
One self-contained HTML file — no build step, no CDN, no dependencies — where:
|
|
70
|
+
|
|
71
|
+
- **observed** — a real `track()` call crossed this edge. Stroke weight scales
|
|
72
|
+
with volume.
|
|
73
|
+
- **declared, never seen** — the catalog says this happens; this run never saw
|
|
74
|
+
it. Drawn dashed and grey, and it never moves in any mode. **The stillness is
|
|
75
|
+
the finding.**
|
|
76
|
+
- **ran, not in the catalog** — it happened and nobody wrote it down.
|
|
77
|
+
- **consumer asserted** — see below.
|
|
78
|
+
|
|
79
|
+
### Three modes, because motion is a claim
|
|
80
|
+
|
|
81
|
+
| `--mode` | What moves | Use for |
|
|
82
|
+
| -------- | -------------------------------------------------------------------- | --------------------------------- |
|
|
83
|
+
| `static` | Nothing. Weight and dash carry the message. | Commit it; read it in a PR |
|
|
84
|
+
| `replay` | Markers cross observed edges at a rate drawn from the snapshot count | A file that shows relative volume |
|
|
85
|
+
| `live` | A marker crosses an edge when that event actually fires | A wall screen, a demo |
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
autotel-eventcatalog map --snapshot snap.json --catalog ./catalog \
|
|
89
|
+
--output map.html --mode live --live-url /events --live-event track
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Live mode subscribes to an SSE endpoint you serve and keys each frame's `name`
|
|
93
|
+
against the edges it belongs to. `--live-event` names the SSE event to listen
|
|
94
|
+
for; the default is `message`, so a server that writes `event: track` needs it
|
|
95
|
+
set or the page sits silently claiming to be live.
|
|
96
|
+
|
|
97
|
+
### Telling a rename from two problems
|
|
98
|
+
|
|
99
|
+
`normaliseEventId` absorbs case and `._-`, so `payment.succeeded` does not match
|
|
100
|
+
a catalog `PaymentCaptured`. The drift report then shows two findings — one
|
|
101
|
+
event observed but undocumented, one documented but never observed — that are
|
|
102
|
+
really one event under two names. Reporting a rename as two unrelated problems
|
|
103
|
+
is the fastest way for a team to stop believing the tool.
|
|
104
|
+
|
|
105
|
+
`map` and `drift` both pair them up and say so:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
## Possible renames
|
|
109
|
+
|
|
110
|
+
- `recommendation.created` is probably `RecommendationGenerated` renamed
|
|
111
|
+
(83% similar) — rename one side, or document it as a new event.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Both findings stay listed. A suggestion is a question for a human, not a fact
|
|
115
|
+
about the system: on the day a rename is _not_ what happened, one of those two
|
|
116
|
+
findings is a real removal, and collapsing them would hide it.
|
|
117
|
+
|
|
118
|
+
### Testing the live page
|
|
119
|
+
|
|
120
|
+
`pnpm test` covers the model and the generated artifact. The page's own
|
|
121
|
+
behaviour is covered separately, in Chromium:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pnpm test:browser
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Those tests install a controllable `EventSource` before the page script runs,
|
|
128
|
+
deliver exact frames, and read the resulting DOM — what a frame credits, what an
|
|
129
|
+
unknown producer does to the counters, whether a never-seen edge stays still.
|
|
130
|
+
Asserting on the emitted JavaScript instead would only prove the lines were
|
|
131
|
+
typed, which is what let every live defect through.
|
|
132
|
+
|
|
133
|
+
### The honesty rule
|
|
134
|
+
|
|
135
|
+
Two things are kept apart on purpose:
|
|
136
|
+
|
|
137
|
+
- **liveness** — catalog vs snapshot presence.
|
|
138
|
+
- **evidence** — whether the runtime _could_ have seen it. A producer writes the
|
|
139
|
+
event, so `produces` and `publishes-to` edges carry `observed` evidence. **A
|
|
140
|
+
consumer edge is always `asserted`**, however busy the event is: a producer's
|
|
141
|
+
telemetry proves an event fired and can never prove anyone received it.
|
|
142
|
+
Asserted edges move with a _hollow_ marker.
|
|
143
|
+
|
|
144
|
+
Collapsing those into one axis is how a diagram ends up implying it watched
|
|
145
|
+
something it only read. `map` will not do that, and a unit test holds the line.
|
|
146
|
+
|
|
147
|
+
Consumption becomes observable once consumers are instrumented too; until then
|
|
148
|
+
the picture says which half it knows.
|
|
149
|
+
|
|
47
150
|
## Install
|
|
48
151
|
|
|
49
152
|
```bash
|
package/dist/cli.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
const
|
|
2
|
+
const require_map_html = require('./map-html-AxfF9V_g.cjs');
|
|
3
3
|
let node_fs_promises = require("node:fs/promises");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
|
+
node_path = require_map_html.__toESM(node_path, 1);
|
|
5
6
|
let node_url = require("node:url");
|
|
6
7
|
|
|
7
8
|
//#region src/cli.ts
|
|
@@ -29,7 +30,7 @@ function isRenderer(value) {
|
|
|
29
30
|
return typeof r.name === "string" && typeof r.description === "string" && typeof r.renderReport === "function" && typeof r.renderDelta === "function";
|
|
30
31
|
}
|
|
31
32
|
async function loadRendererModule(modulePath) {
|
|
32
|
-
const resolved =
|
|
33
|
+
const resolved = node_path.default.resolve(process.cwd(), modulePath);
|
|
33
34
|
let mod;
|
|
34
35
|
try {
|
|
35
36
|
mod = await import((0, node_url.pathToFileURL)(resolved).href);
|
|
@@ -43,7 +44,7 @@ async function loadRendererModule(modulePath) {
|
|
|
43
44
|
process.exit(2);
|
|
44
45
|
}
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
+
require_map_html.registerRenderer(candidate);
|
|
47
48
|
} catch (error) {
|
|
48
49
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
49
50
|
process.exit(2);
|
|
@@ -76,10 +77,11 @@ async function processRegisterRendererFlags(argv) {
|
|
|
76
77
|
}
|
|
77
78
|
function parseArgs(argv) {
|
|
78
79
|
const [command, ...rest] = argv;
|
|
79
|
-
if (command !== "drift" && command !== "stamp" && command !== "generate") {
|
|
80
|
+
if (command !== "drift" && command !== "stamp" && command !== "generate" && command !== "map") {
|
|
80
81
|
usage();
|
|
81
82
|
process.exit(2);
|
|
82
83
|
}
|
|
84
|
+
if (command === "map") return parseMapArgs(rest);
|
|
83
85
|
if (command === "generate") return parseGenerateArgs(rest);
|
|
84
86
|
if (command === "stamp") return parseStampArgs(rest);
|
|
85
87
|
return parseDriftArgs(rest);
|
|
@@ -125,8 +127,8 @@ function parseDriftArgs(rest) {
|
|
|
125
127
|
}
|
|
126
128
|
case "--format": {
|
|
127
129
|
const value = requireValue(rest, ++i, "--format");
|
|
128
|
-
if (!
|
|
129
|
-
process.stderr.write(`Invalid --format value: ${value}. Available renderers: ${
|
|
130
|
+
if (!require_map_html.getRenderer(value)) {
|
|
131
|
+
process.stderr.write(`Invalid --format value: ${value}. Available renderers: ${require_map_html.listRendererNames().join(", ")}.\n`);
|
|
130
132
|
process.exit(2);
|
|
131
133
|
}
|
|
132
134
|
format = value;
|
|
@@ -154,11 +156,11 @@ function parseDriftArgs(rest) {
|
|
|
154
156
|
}
|
|
155
157
|
return {
|
|
156
158
|
command: "drift",
|
|
157
|
-
snapshot:
|
|
158
|
-
baseSnapshot: baseSnapshot ?
|
|
159
|
-
catalog:
|
|
160
|
-
output: output ?
|
|
161
|
-
summaryOutput: summaryOutput ?
|
|
159
|
+
snapshot: node_path.default.resolve(snapshot),
|
|
160
|
+
baseSnapshot: baseSnapshot ? node_path.default.resolve(baseSnapshot) : void 0,
|
|
161
|
+
catalog: node_path.default.resolve(catalog),
|
|
162
|
+
output: output ? node_path.default.resolve(output) : void 0,
|
|
163
|
+
summaryOutput: summaryOutput ? node_path.default.resolve(summaryOutput) : void 0,
|
|
162
164
|
failOnDrift,
|
|
163
165
|
policy,
|
|
164
166
|
format
|
|
@@ -206,11 +208,11 @@ function parseStampArgs(rest) {
|
|
|
206
208
|
}
|
|
207
209
|
return {
|
|
208
210
|
command: "stamp",
|
|
209
|
-
snapshot:
|
|
210
|
-
catalog:
|
|
211
|
+
snapshot: node_path.default.resolve(snapshot),
|
|
212
|
+
catalog: node_path.default.resolve(catalog),
|
|
211
213
|
dryRun,
|
|
212
214
|
format,
|
|
213
|
-
summaryOutput: summaryOutput ?
|
|
215
|
+
summaryOutput: summaryOutput ? node_path.default.resolve(summaryOutput) : void 0
|
|
214
216
|
};
|
|
215
217
|
}
|
|
216
218
|
function parseGenerateArgs(rest) {
|
|
@@ -263,13 +265,86 @@ function parseGenerateArgs(rest) {
|
|
|
263
265
|
}
|
|
264
266
|
return {
|
|
265
267
|
command: "generate",
|
|
266
|
-
snapshot:
|
|
267
|
-
catalog:
|
|
268
|
+
snapshot: node_path.default.resolve(snapshot),
|
|
269
|
+
catalog: node_path.default.resolve(catalog),
|
|
268
270
|
dryRun,
|
|
269
271
|
edgesOnly,
|
|
270
272
|
version,
|
|
271
273
|
format,
|
|
272
|
-
summaryOutput: summaryOutput ?
|
|
274
|
+
summaryOutput: summaryOutput ? node_path.default.resolve(summaryOutput) : void 0
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function parseMapMode(value) {
|
|
278
|
+
if (value === "static" || value === "replay" || value === "live") return value;
|
|
279
|
+
process.stderr.write(`Invalid --mode: ${value}. Expected 'static' | 'replay' | 'live'.\n`);
|
|
280
|
+
process.exit(2);
|
|
281
|
+
}
|
|
282
|
+
function parseMapArgs(rest) {
|
|
283
|
+
let snapshot;
|
|
284
|
+
let catalog;
|
|
285
|
+
let output;
|
|
286
|
+
let mode = "static";
|
|
287
|
+
let liveUrl;
|
|
288
|
+
let liveEventName;
|
|
289
|
+
let title;
|
|
290
|
+
let summaryOutput;
|
|
291
|
+
for (let i = 0; i < rest.length; i++) {
|
|
292
|
+
const arg = rest[i];
|
|
293
|
+
switch (arg) {
|
|
294
|
+
case "--snapshot":
|
|
295
|
+
snapshot = requireValue(rest, ++i, "--snapshot");
|
|
296
|
+
break;
|
|
297
|
+
case "--catalog":
|
|
298
|
+
catalog = requireValue(rest, ++i, "--catalog");
|
|
299
|
+
break;
|
|
300
|
+
case "--output":
|
|
301
|
+
output = requireValue(rest, ++i, "--output");
|
|
302
|
+
break;
|
|
303
|
+
case "--mode":
|
|
304
|
+
mode = parseMapMode(requireValue(rest, ++i, "--mode"));
|
|
305
|
+
break;
|
|
306
|
+
case "--live-url":
|
|
307
|
+
liveUrl = requireValue(rest, ++i, "--live-url");
|
|
308
|
+
break;
|
|
309
|
+
case "--live-event":
|
|
310
|
+
liveEventName = requireValue(rest, ++i, "--live-event");
|
|
311
|
+
break;
|
|
312
|
+
case "--title":
|
|
313
|
+
title = requireValue(rest, ++i, "--title");
|
|
314
|
+
break;
|
|
315
|
+
case "--summary-output":
|
|
316
|
+
summaryOutput = requireValue(rest, ++i, "--summary-output");
|
|
317
|
+
break;
|
|
318
|
+
case "-h":
|
|
319
|
+
case "--help":
|
|
320
|
+
usage();
|
|
321
|
+
process.exit(0);
|
|
322
|
+
break;
|
|
323
|
+
default:
|
|
324
|
+
process.stderr.write(`Unknown argument: ${arg}\n`);
|
|
325
|
+
usage();
|
|
326
|
+
process.exit(2);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!snapshot || !catalog || !output) {
|
|
330
|
+
process.stderr.write("All of --snapshot, --catalog and --output are required for map.\n");
|
|
331
|
+
usage();
|
|
332
|
+
process.exit(2);
|
|
333
|
+
}
|
|
334
|
+
if (mode === "live" && !liveUrl) {
|
|
335
|
+
process.stderr.write("--mode live requires --live-url <sse endpoint>.\n");
|
|
336
|
+
process.exit(2);
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
command: "map",
|
|
340
|
+
snapshot: node_path.default.resolve(snapshot),
|
|
341
|
+
catalog: node_path.default.resolve(catalog),
|
|
342
|
+
output: node_path.default.resolve(output),
|
|
343
|
+
mode,
|
|
344
|
+
liveUrl,
|
|
345
|
+
liveEventName,
|
|
346
|
+
title,
|
|
347
|
+
summaryOutput: summaryOutput ? node_path.default.resolve(summaryOutput) : void 0
|
|
273
348
|
};
|
|
274
349
|
}
|
|
275
350
|
function usage() {
|
|
@@ -278,6 +353,7 @@ function usage() {
|
|
|
278
353
|
" autotel-eventcatalog drift --snapshot <path> --catalog <path> [options]",
|
|
279
354
|
" autotel-eventcatalog stamp --snapshot <path> --catalog <path> [--dry-run]",
|
|
280
355
|
" autotel-eventcatalog generate --snapshot <path> --catalog <path> [options]",
|
|
356
|
+
" autotel-eventcatalog map --snapshot <path> --catalog <path> --output <path.html>",
|
|
281
357
|
"",
|
|
282
358
|
"Global options (any command):",
|
|
283
359
|
" --register-renderer <module> Dynamically import a Renderer (default export",
|
|
@@ -292,7 +368,7 @@ function usage() {
|
|
|
292
368
|
" --output <path> Write the report to this file",
|
|
293
369
|
" --summary-output <path> Write a machine-readable drift summary JSON file",
|
|
294
370
|
" --policy <mode> Drift fail policy: 'all' | 'new-only'",
|
|
295
|
-
` --format <kind> Output format: ${
|
|
371
|
+
` --format <kind> Output format: ${require_map_html.listRendererNames().join(" | ")}`,
|
|
296
372
|
" --fail-on-drift Exit non-zero when policy marks drift as failing",
|
|
297
373
|
"",
|
|
298
374
|
"stamp options:",
|
|
@@ -311,24 +387,34 @@ function usage() {
|
|
|
311
387
|
" --format <kind> Output format: 'text' (default) | 'json'",
|
|
312
388
|
" --summary-output <path> Write a machine-readable generate summary JSON file",
|
|
313
389
|
"",
|
|
390
|
+
"map options:",
|
|
391
|
+
" --snapshot <path> Architecture snapshot JSON",
|
|
392
|
+
" --catalog <path> EventCatalog root",
|
|
393
|
+
" --output <path> Write the self-contained HTML map here",
|
|
394
|
+
" --mode <kind> 'static' (default) | 'replay' | 'live'",
|
|
395
|
+
" --live-url <url> SSE endpoint of live events (required for live)",
|
|
396
|
+
" --live-event <name> SSE event name to listen for (default: 'message')",
|
|
397
|
+
" --title <text> Override the page title",
|
|
398
|
+
" --summary-output <path> Write the machine-readable live map JSON",
|
|
399
|
+
"",
|
|
314
400
|
" -h, --help Show this help",
|
|
315
401
|
""
|
|
316
402
|
].join("\n"));
|
|
317
403
|
}
|
|
318
404
|
async function runDrift(args) {
|
|
319
|
-
const headSnapshot = await
|
|
320
|
-
const catalog = await
|
|
321
|
-
const headReport =
|
|
322
|
-
const delta = args.baseSnapshot ?
|
|
405
|
+
const headSnapshot = await require_map_html.loadSnapshot(args.snapshot);
|
|
406
|
+
const catalog = await require_map_html.readCatalogState(args.catalog);
|
|
407
|
+
const headReport = require_map_html.diffCatalogAgainstSnapshot(headSnapshot, catalog);
|
|
408
|
+
const delta = args.baseSnapshot ? require_map_html.compareDriftReports(require_map_html.diffCatalogAgainstSnapshot(await require_map_html.loadSnapshot(args.baseSnapshot), catalog), headReport) : void 0;
|
|
323
409
|
const effectivePolicy = args.policy ?? (delta ? "new-only" : "all");
|
|
324
|
-
const policyResult = effectivePolicy === "new-only" && delta ?
|
|
410
|
+
const policyResult = effectivePolicy === "new-only" && delta ? require_map_html.evaluatePolicy({
|
|
325
411
|
mode: "new-only",
|
|
326
412
|
delta
|
|
327
|
-
}) :
|
|
413
|
+
}) : require_map_html.evaluatePolicy({
|
|
328
414
|
mode: "all",
|
|
329
415
|
report: headReport
|
|
330
416
|
});
|
|
331
|
-
const renderer =
|
|
417
|
+
const renderer = require_map_html.getRenderer(args.format);
|
|
332
418
|
if (!renderer) {
|
|
333
419
|
process.stderr.write(`Unknown renderer: ${args.format}\n`);
|
|
334
420
|
process.exit(2);
|
|
@@ -337,13 +423,13 @@ async function runDrift(args) {
|
|
|
337
423
|
process.stdout.write(output);
|
|
338
424
|
if (!output.endsWith("\n")) process.stdout.write("\n");
|
|
339
425
|
if (args.output) {
|
|
340
|
-
await (0, node_fs_promises.mkdir)(
|
|
426
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.output), { recursive: true });
|
|
341
427
|
await (0, node_fs_promises.writeFile)(args.output, output, "utf8");
|
|
342
428
|
process.stderr.write(`\nWrote drift report: ${args.output}\n`);
|
|
343
429
|
}
|
|
344
430
|
const summary = buildSummary(effectivePolicy, headReport, delta, policyResult);
|
|
345
431
|
if (args.summaryOutput) {
|
|
346
|
-
await (0, node_fs_promises.mkdir)(
|
|
432
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.summaryOutput), { recursive: true });
|
|
347
433
|
await (0, node_fs_promises.writeFile)(args.summaryOutput, JSON.stringify(summary, null, 2), "utf8");
|
|
348
434
|
process.stderr.write(`Wrote drift summary: ${args.summaryOutput}\n`);
|
|
349
435
|
}
|
|
@@ -352,7 +438,7 @@ async function runDrift(args) {
|
|
|
352
438
|
}
|
|
353
439
|
const DRIFT_SUMMARY_SPEC = "autotel-eventcatalog-drift-summary/v0.2.0";
|
|
354
440
|
function buildSummary(mode, headReport, delta, policyResult) {
|
|
355
|
-
const counts = mode === "new-only" && delta ?
|
|
441
|
+
const counts = mode === "new-only" && delta ? require_map_html.countDriftEntries(delta.introduced) : require_map_html.countDriftReport(headReport);
|
|
356
442
|
return {
|
|
357
443
|
spec: DRIFT_SUMMARY_SPEC,
|
|
358
444
|
mode,
|
|
@@ -362,12 +448,12 @@ function buildSummary(mode, headReport, delta, policyResult) {
|
|
|
362
448
|
};
|
|
363
449
|
}
|
|
364
450
|
async function runStamp(args) {
|
|
365
|
-
const result = await
|
|
366
|
-
snapshot: await
|
|
451
|
+
const result = await require_map_html.stampCatalog({
|
|
452
|
+
snapshot: await require_map_html.loadSnapshot(args.snapshot),
|
|
367
453
|
catalogPath: args.catalog,
|
|
368
454
|
dryRun: args.dryRun
|
|
369
455
|
});
|
|
370
|
-
const summary =
|
|
456
|
+
const summary = require_map_html.buildStampSummary(result, args.dryRun);
|
|
371
457
|
if (args.format === "json") process.stdout.write(JSON.stringify({
|
|
372
458
|
summary,
|
|
373
459
|
updates: result.updates,
|
|
@@ -382,20 +468,20 @@ async function runStamp(args) {
|
|
|
382
468
|
process.stderr.write(`\n${summary.changedFiles} changed, ${summary.replaces} replaces, ${summary.inserts} inserts, ${summary.skipped} skipped${args.dryRun ? " (dry run)" : ""}\n`);
|
|
383
469
|
}
|
|
384
470
|
if (args.summaryOutput) {
|
|
385
|
-
await (0, node_fs_promises.mkdir)(
|
|
471
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.summaryOutput), { recursive: true });
|
|
386
472
|
await (0, node_fs_promises.writeFile)(args.summaryOutput, JSON.stringify(summary, null, 2), "utf8");
|
|
387
473
|
process.stderr.write(`Wrote stamp summary: ${args.summaryOutput}\n`);
|
|
388
474
|
}
|
|
389
475
|
}
|
|
390
476
|
async function runGenerate(args) {
|
|
391
|
-
const result = await
|
|
392
|
-
snapshot: await
|
|
477
|
+
const result = await require_map_html.generateCatalogFromSnapshot({
|
|
478
|
+
snapshot: await require_map_html.loadSnapshot(args.snapshot),
|
|
393
479
|
catalogPath: args.catalog,
|
|
394
480
|
dryRun: args.dryRun,
|
|
395
481
|
edgesOnly: args.edgesOnly,
|
|
396
482
|
version: args.version
|
|
397
483
|
});
|
|
398
|
-
const summary =
|
|
484
|
+
const summary = require_map_html.buildGenerateSummary(result, {
|
|
399
485
|
dryRun: args.dryRun,
|
|
400
486
|
edgesOnly: args.edgesOnly
|
|
401
487
|
});
|
|
@@ -411,11 +497,29 @@ async function runGenerate(args) {
|
|
|
411
497
|
if (result.operations.length === 0) process.stdout.write("No generation operations needed.\n");
|
|
412
498
|
}
|
|
413
499
|
if (args.summaryOutput) {
|
|
414
|
-
await (0, node_fs_promises.mkdir)(
|
|
500
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.summaryOutput), { recursive: true });
|
|
415
501
|
await (0, node_fs_promises.writeFile)(args.summaryOutput, JSON.stringify(summary, null, 2), "utf8");
|
|
416
502
|
process.stderr.write(`Wrote generate summary: ${args.summaryOutput}\n`);
|
|
417
503
|
}
|
|
418
504
|
}
|
|
505
|
+
async function runMap(args) {
|
|
506
|
+
const [snapshot, catalog] = await Promise.all([require_map_html.loadSnapshot(args.snapshot), require_map_html.readCatalogState(args.catalog)]);
|
|
507
|
+
const map = require_map_html.buildLiveMap(snapshot, catalog);
|
|
508
|
+
const html = require_map_html.renderLiveMapHtml(map, {
|
|
509
|
+
mode: args.mode,
|
|
510
|
+
...args.liveUrl && { liveUrl: args.liveUrl },
|
|
511
|
+
...args.liveEventName && { liveEventName: args.liveEventName },
|
|
512
|
+
...args.title && { title: args.title }
|
|
513
|
+
});
|
|
514
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.output), { recursive: true });
|
|
515
|
+
await (0, node_fs_promises.writeFile)(args.output, html, "utf8");
|
|
516
|
+
if (args.summaryOutput) {
|
|
517
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(args.summaryOutput), { recursive: true });
|
|
518
|
+
await (0, node_fs_promises.writeFile)(args.summaryOutput, `${JSON.stringify(map, null, 2)}\n`, "utf8");
|
|
519
|
+
}
|
|
520
|
+
const s = map.summary;
|
|
521
|
+
process.stdout.write(`${args.output}\n${s.observedEdges} observed, ${s.declaredOnlyEdges} declared-but-never-seen, ${s.undocumentedEdges} undocumented, ${s.assertedEdges} asserted (mode: ${args.mode})\n`);
|
|
522
|
+
}
|
|
419
523
|
async function main() {
|
|
420
524
|
const args = parseArgs(await processRegisterRendererFlags(process.argv.slice(2)));
|
|
421
525
|
if (args.command === "stamp") {
|
|
@@ -426,6 +530,10 @@ async function main() {
|
|
|
426
530
|
await runGenerate(args);
|
|
427
531
|
return;
|
|
428
532
|
}
|
|
533
|
+
if (args.command === "map") {
|
|
534
|
+
await runMap(args);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
429
537
|
await runDrift(args);
|
|
430
538
|
}
|
|
431
539
|
main().catch((error) => {
|