autotel-eventcatalog 1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/CONTRIBUTING.md +212 -0
  3. package/README.md +307 -0
  4. package/action.yml +155 -0
  5. package/dist/cli.cjs +1071 -0
  6. package/dist/cli.cjs.map +1 -0
  7. package/dist/cli.d.cts +2 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +1065 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.cjs +794 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +267 -0
  14. package/dist/index.d.ts +267 -0
  15. package/dist/index.js +764 -0
  16. package/dist/index.js.map +1 -0
  17. package/docs/CONTRACT.md +280 -0
  18. package/docs/EXTENDING.md +248 -0
  19. package/docs/TROUBLESHOOTING.md +220 -0
  20. package/docs/UPGRADING.md +202 -0
  21. package/package.json +78 -0
  22. package/schemas/README.md +44 -0
  23. package/schemas/drift-report-v0.1.0.json +107 -0
  24. package/schemas/drift-report-v0.2.0.json +137 -0
  25. package/schemas/drift-summary-v0.1.0.json +74 -0
  26. package/schemas/drift-summary-v0.2.0.json +74 -0
  27. package/schemas/stamp-summary-v0.1.0.json +54 -0
  28. package/src/__fixtures__/drift-report-all.golden.json +33 -0
  29. package/src/__fixtures__/drift-summary-clean.golden.json +17 -0
  30. package/src/__fixtures__/drift-summary-drifty.golden.json +17 -0
  31. package/src/__fixtures__/stamp-summary-noop.golden.json +10 -0
  32. package/src/catalog.test.ts +63 -0
  33. package/src/catalog.ts +169 -0
  34. package/src/cli.e2e.test.ts +310 -0
  35. package/src/cli.ts +402 -0
  36. package/src/contract.test.ts +395 -0
  37. package/src/diff-vs-base.test.ts +145 -0
  38. package/src/diff-vs-base.ts +242 -0
  39. package/src/diff.test.ts +384 -0
  40. package/src/diff.ts +296 -0
  41. package/src/index.ts +73 -0
  42. package/src/policy.test.ts +75 -0
  43. package/src/policy.ts +41 -0
  44. package/src/renderers/index.ts +35 -0
  45. package/src/renderers/json.ts +33 -0
  46. package/src/renderers/markdown.ts +223 -0
  47. package/src/renderers/renderers.test.ts +79 -0
  48. package/src/renderers/terminal.ts +30 -0
  49. package/src/renderers/types.ts +26 -0
  50. package/src/report.test.ts +205 -0
  51. package/src/report.ts +27 -0
  52. package/src/snapshot.ts +25 -0
  53. package/src/stamp.test.ts +283 -0
  54. package/src/stamp.ts +232 -0
@@ -0,0 +1,267 @@
1
+ import { ArchitectureSnapshot, EventObservation } from 'autotel-subscribers/architecture-snapshot';
2
+ export { ArchitectureSnapshot, EventObservation } from 'autotel-subscribers/architecture-snapshot';
3
+ import { Channel, Event, Service } from '@eventcatalog/sdk';
4
+
5
+ declare function loadSnapshot(path: string): Promise<ArchitectureSnapshot>;
6
+
7
+ type SchemaConstraint = {
8
+ types?: string[];
9
+ enumValues?: unknown[];
10
+ };
11
+ type CatalogEvent = Event & {
12
+ /** Absolute path to the event's `index.mdx`, resolved via SDK. */
13
+ filePath: string;
14
+ /** Field paths declared in the event's JSON Schema (if present). */
15
+ declaredFieldPaths?: string[];
16
+ declaredSchemaConstraints?: Record<string, SchemaConstraint>;
17
+ };
18
+ type CatalogService = Service & {
19
+ filePath: string;
20
+ };
21
+ type CatalogChannel = Channel & {
22
+ filePath: string;
23
+ };
24
+ type CatalogState = {
25
+ events: Map<string, CatalogEvent>;
26
+ services: Map<string, CatalogService>;
27
+ channels: Map<string, CatalogChannel>;
28
+ };
29
+ declare function readCatalogState(catalogPath: string): Promise<CatalogState>;
30
+ /**
31
+ * Extract field paths from a JSON Schema. Mirrors the dotted-path convention
32
+ * used by the snapshot subscriber: arrays collapse to `[]`, nested objects
33
+ * use `.`. We walk `properties` (objects) and `items` (arrays).
34
+ */
35
+ declare function extractDeclaredFieldPaths(schema: unknown, prefix?: string): string[];
36
+
37
+ type EventDrift = {
38
+ /** Event names observed in the snapshot but not present in the catalog. */
39
+ observedButUndocumented: string[];
40
+ /** Event names declared in the catalog but never observed in the snapshot. */
41
+ documentedButUnseen: string[];
42
+ /** Per-event field-path mismatches. */
43
+ fieldDrift: FieldDrift[];
44
+ /** Per-event field type mismatches against declared schema types. */
45
+ typeDrift: TypeDrift[];
46
+ /** Per-event enum/value mismatches against declared schema enums. */
47
+ valueDrift: ValueDrift[];
48
+ };
49
+ type FieldDrift = {
50
+ event: string;
51
+ /** Field paths in the observed payload but not declared in the schema. */
52
+ extra: string[];
53
+ /** Field paths declared in the schema but never observed in a payload. */
54
+ missing: string[];
55
+ };
56
+ type TypeDrift = {
57
+ event: string;
58
+ path: string;
59
+ declared: string[];
60
+ observed: string[];
61
+ };
62
+ type ValueDrift = {
63
+ event: string;
64
+ path: string;
65
+ declared: unknown[];
66
+ observed: unknown[];
67
+ };
68
+ type ServiceDrift = {
69
+ /** Producers in the snapshot but not present in the catalog as services. */
70
+ observedButUndocumented: string[];
71
+ };
72
+ type ChannelDrift = {
73
+ /** Channels in the snapshot but not present in the catalog. */
74
+ observedButUndocumented: string[];
75
+ };
76
+ type DriftReport = {
77
+ snapshotGeneratedAt: string;
78
+ snapshotService: string;
79
+ events: EventDrift;
80
+ services: ServiceDrift;
81
+ channels: ChannelDrift;
82
+ };
83
+ /** True if the report contains any drift worth surfacing in a PR check. */
84
+ declare function hasDrift(report: DriftReport): boolean;
85
+ /**
86
+ * Per-category counts for a DriftReport. Keeps the dashboard's hero meter,
87
+ * the CLI's summary-output, and the action's structured output all agreeing
88
+ * on what "N findings" means.
89
+ */
90
+ type DriftCounts = {
91
+ /** Total of all categories — what a dashboard "drift findings" badge shows. */
92
+ total: number;
93
+ observedButUndocumentedEvents: number;
94
+ documentedButUnseenEvents: number;
95
+ /** Number of distinct events with field-path drift entries. */
96
+ fieldDriftEvents: number;
97
+ /** Sum of every individual extra + missing path across all fieldDrift entries. */
98
+ fieldDriftPaths: number;
99
+ typeDriftPaths: number;
100
+ valueDriftPaths: number;
101
+ undocumentedServices: number;
102
+ undocumentedChannels: number;
103
+ };
104
+ declare function countDriftReport(report: DriftReport): DriftCounts;
105
+ declare function diffCatalogAgainstSnapshot(snapshot: ArchitectureSnapshot, catalog: CatalogState): DriftReport;
106
+
107
+ type DriftDelta = {
108
+ /** Drift entries present in head but not in base. */
109
+ introduced: DriftEntries;
110
+ /** Drift entries present in base but not in head — the PR fixed these. */
111
+ resolved: DriftEntries;
112
+ /** True if `introduced` has any non-empty section. */
113
+ hasNewDrift: boolean;
114
+ };
115
+ type DriftEntries = {
116
+ events: {
117
+ observedButUndocumented: string[];
118
+ documentedButUnseen: string[];
119
+ fieldDrift: FieldDrift[];
120
+ typeDrift: TypeDrift[];
121
+ valueDrift: ValueDrift[];
122
+ };
123
+ services: {
124
+ observedButUndocumented: string[];
125
+ };
126
+ channels: {
127
+ observedButUndocumented: string[];
128
+ };
129
+ };
130
+ declare function compareDriftReports(base: DriftReport, head: DriftReport): DriftDelta;
131
+ /**
132
+ * Per-category counts for one side of a DriftDelta (introduced or resolved).
133
+ * Same shape as DriftCounts so dashboards and CI can render the
134
+ * introduced/resolved sections with identical accounting.
135
+ */
136
+ declare function countDriftEntries(entries: DriftEntries): DriftCounts;
137
+ declare function countDriftDelta(delta: DriftDelta): {
138
+ introduced: DriftCounts;
139
+ resolved: DriftCounts;
140
+ };
141
+
142
+ type RenderReport = (report: DriftReport) => string;
143
+ type RenderDelta = (delta: DriftDelta) => string;
144
+ interface Renderer {
145
+ /** Short name used by the CLI's `--format` flag. */
146
+ readonly name: string;
147
+ /** One-line human description shown in CLI help and the README. */
148
+ readonly description: string;
149
+ readonly renderReport: RenderReport;
150
+ readonly renderDelta: RenderDelta;
151
+ }
152
+
153
+ declare function renderMarkdown(report: DriftReport): string;
154
+ /**
155
+ * Render the diff-of-diffs as a PR-comment-friendly markdown block. Sections
156
+ * appear only when they have content, so a clean PR produces a tight message.
157
+ */
158
+ declare function renderDeltaMarkdown(delta: DriftDelta): string;
159
+
160
+ declare function renderTerminal(report: DriftReport): string;
161
+ declare function renderDeltaTerminal(delta: DriftDelta): string;
162
+
163
+ /**
164
+ * Versioned identifier baked into the JSON envelope. Bumping it is a
165
+ * breaking change for downstream consumers — add fields rather than rename.
166
+ */
167
+ declare const REPORT_SPEC: "autotel-eventcatalog-report/v0.2.0";
168
+ type JsonReport = {
169
+ mode: 'all';
170
+ report: DriftReport;
171
+ } | {
172
+ mode: 'new-only';
173
+ delta: DriftDelta;
174
+ };
175
+ type JsonReportEnvelope = {
176
+ spec: typeof REPORT_SPEC;
177
+ } & JsonReport;
178
+ declare function renderJson(data: JsonReport): string;
179
+
180
+ declare const RENDERERS: readonly Renderer[];
181
+ declare const RENDERER_NAMES: string[];
182
+ declare function getRenderer(name: string): Renderer | undefined;
183
+ type RendererName = (typeof RENDERER_NAMES)[number];
184
+
185
+ type DriftPolicyMode = 'all' | 'new-only';
186
+ type PolicyEvaluationInput = {
187
+ mode: 'all';
188
+ report: DriftReport;
189
+ } | {
190
+ mode: 'new-only';
191
+ delta: DriftDelta;
192
+ };
193
+ type PolicyEvaluationResult = {
194
+ shouldFail: boolean;
195
+ reason: string;
196
+ };
197
+ declare function evaluatePolicy(input: PolicyEvaluationInput): PolicyEvaluationResult;
198
+
199
+ declare const STAMP_START = "<!-- autotel:stamp-start -->";
200
+ declare const STAMP_END = "<!-- autotel:stamp-end -->";
201
+ interface StampOptions {
202
+ /** Loaded snapshot, OR pass `loadSnapshot(path)` from the caller. */
203
+ snapshot: ArchitectureSnapshot;
204
+ /** Catalog root (the directory containing eventcatalog.config.*). */
205
+ catalogPath: string;
206
+ /** If true, do not write files — just return the diff plan. */
207
+ dryRun?: boolean;
208
+ /** Override "now" for deterministic tests. */
209
+ now?: () => Date;
210
+ }
211
+ type StampUpdate = {
212
+ /** Catalog event id, e.g. `OrderPlaced`. */
213
+ catalogId: string;
214
+ /** Snapshot event name, e.g. `order.placed`. */
215
+ snapshotName: string;
216
+ /** Absolute path to the mdx file that was (or would be) updated. */
217
+ filePath: string;
218
+ /** Was this an insert (no prior markers) or a replace? */
219
+ action: 'insert' | 'replace';
220
+ /**
221
+ * True if the proposed content differs from what's on disk. False when a
222
+ * replace would write byte-identical content — meaning no real change.
223
+ * Used by `--summary-output` so CI can answer "did this PR need stamping?"
224
+ * without diffing files.
225
+ */
226
+ changed: boolean;
227
+ };
228
+ type StampSkip = {
229
+ snapshotName: string;
230
+ reason: 'no-catalog-match';
231
+ };
232
+ type StampResult = {
233
+ updates: StampUpdate[];
234
+ skips: StampSkip[];
235
+ };
236
+ declare function stampCatalog(opts: StampOptions): Promise<StampResult>;
237
+ /**
238
+ * Render the evidence block. Designed to be readable in raw mdx AND visually
239
+ * distinct when rendered by EventCatalog (uses the existing
240
+ * `.evidence-callout` class, plus a small header label).
241
+ */
242
+ declare function buildStampBlock(obs: EventObservation): string;
243
+ /** Versioned identifier for the stamp summary JSON file. */
244
+ declare const STAMP_SUMMARY_SPEC: "autotel-eventcatalog-stamp-summary/v0.1.0";
245
+ type StampSummary = {
246
+ spec: typeof STAMP_SUMMARY_SPEC;
247
+ dryRun: boolean;
248
+ /** Total snapshot events the stamp run considered (matched + skipped). */
249
+ attempted: number;
250
+ /** Skipped events (no catalog match). */
251
+ skipped: number;
252
+ /** Matched events that resulted in an insert action. */
253
+ inserts: number;
254
+ /** Matched events that resulted in a replace action. */
255
+ replaces: number;
256
+ /** Number of files whose content actually changed (or would change in dry-run). */
257
+ changedFiles: number;
258
+ /**
259
+ * True when the run produced (or would produce) any real change. CI can
260
+ * gate on this: "if the committed catalog is stamped, this should be
261
+ * false after running stamp; if not, the PR forgot to re-stamp."
262
+ */
263
+ hadChanges: boolean;
264
+ };
265
+ declare function buildStampSummary(result: StampResult, dryRun: boolean): StampSummary;
266
+
267
+ export { type CatalogChannel, type CatalogEvent, type CatalogService, type CatalogState, type ChannelDrift, type DriftCounts, type DriftDelta, type DriftEntries, type DriftPolicyMode, type DriftReport, type EventDrift, type FieldDrift, type JsonReport, type JsonReportEnvelope, type PolicyEvaluationInput, type PolicyEvaluationResult, RENDERERS, RENDERER_NAMES, REPORT_SPEC, type Renderer, type RendererName, STAMP_END, STAMP_START, STAMP_SUMMARY_SPEC, type ServiceDrift, type StampOptions, type StampResult, type StampSkip, type StampSummary, type StampUpdate, buildStampBlock, buildStampSummary, compareDriftReports, countDriftDelta, countDriftEntries, countDriftReport, diffCatalogAgainstSnapshot, evaluatePolicy, extractDeclaredFieldPaths, getRenderer, hasDrift, loadSnapshot, readCatalogState, renderDeltaMarkdown, renderDeltaTerminal, renderJson, renderMarkdown, renderTerminal, stampCatalog };