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.
- package/CHANGELOG.md +196 -0
- package/CONTRIBUTING.md +212 -0
- package/README.md +307 -0
- package/action.yml +155 -0
- package/dist/cli.cjs +1071 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1065 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +794 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +267 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +764 -0
- package/dist/index.js.map +1 -0
- package/docs/CONTRACT.md +280 -0
- package/docs/EXTENDING.md +248 -0
- package/docs/TROUBLESHOOTING.md +220 -0
- package/docs/UPGRADING.md +202 -0
- package/package.json +78 -0
- package/schemas/README.md +44 -0
- package/schemas/drift-report-v0.1.0.json +107 -0
- package/schemas/drift-report-v0.2.0.json +137 -0
- package/schemas/drift-summary-v0.1.0.json +74 -0
- package/schemas/drift-summary-v0.2.0.json +74 -0
- package/schemas/stamp-summary-v0.1.0.json +54 -0
- package/src/__fixtures__/drift-report-all.golden.json +33 -0
- package/src/__fixtures__/drift-summary-clean.golden.json +17 -0
- package/src/__fixtures__/drift-summary-drifty.golden.json +17 -0
- package/src/__fixtures__/stamp-summary-noop.golden.json +10 -0
- package/src/catalog.test.ts +63 -0
- package/src/catalog.ts +169 -0
- package/src/cli.e2e.test.ts +310 -0
- package/src/cli.ts +402 -0
- package/src/contract.test.ts +395 -0
- package/src/diff-vs-base.test.ts +145 -0
- package/src/diff-vs-base.ts +242 -0
- package/src/diff.test.ts +384 -0
- package/src/diff.ts +296 -0
- package/src/index.ts +73 -0
- package/src/policy.test.ts +75 -0
- package/src/policy.ts +41 -0
- package/src/renderers/index.ts +35 -0
- package/src/renderers/json.ts +33 -0
- package/src/renderers/markdown.ts +223 -0
- package/src/renderers/renderers.test.ts +79 -0
- package/src/renderers/terminal.ts +30 -0
- package/src/renderers/types.ts +26 -0
- package/src/report.test.ts +205 -0
- package/src/report.ts +27 -0
- package/src/snapshot.ts +25 -0
- package/src/stamp.test.ts +283 -0
- package/src/stamp.ts +232 -0
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# autotel-eventcatalog
|
|
2
|
+
|
|
3
|
+
Keep your [EventCatalog](https://www.eventcatalog.dev) honest about what
|
|
4
|
+
the code actually does at runtime.
|
|
5
|
+
|
|
6
|
+
> **New here?** [autotel](https://github.com/jagreehal/autotel) is an
|
|
7
|
+
> ergonomic OpenTelemetry wrapper for Node.js: you call `trace()`, `span()`
|
|
8
|
+
> and `track(eventName, payload)` in your service code and it emits spans
|
|
9
|
+
> and domain events. The `ArchitectureSnapshotSubscriber` from
|
|
10
|
+
> `autotel-subscribers` listens to those events during a test run and
|
|
11
|
+
> writes a `snapshot.json` describing every event that fired, the fields
|
|
12
|
+
> in its payload, the runtime types of those fields, who produced it and
|
|
13
|
+
> on what channel. This package reads that snapshot and compares it to
|
|
14
|
+
> your catalog.
|
|
15
|
+
|
|
16
|
+
Two tools:
|
|
17
|
+
|
|
18
|
+
| Command | Mode | What it does |
|
|
19
|
+
| ----------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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
|
+
| **`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. |
|
|
22
|
+
|
|
23
|
+
Both share inputs: an autotel snapshot JSON file and an EventCatalog
|
|
24
|
+
directory. Both ship a versioned JSON summary you can gate CI on. The
|
|
25
|
+
`drift` command also ships as a one-line GitHub Action with a sticky PR
|
|
26
|
+
comment.
|
|
27
|
+
|
|
28
|
+
Same model as Pact, for event architectures.
|
|
29
|
+
|
|
30
|
+
## What this package does NOT do
|
|
31
|
+
|
|
32
|
+
To keep the scope tight:
|
|
33
|
+
|
|
34
|
+
- **Does not produce snapshots.** Snapshots come from
|
|
35
|
+
[`autotel-subscribers`'s `ArchitectureSnapshotSubscriber`](../autotel-subscribers).
|
|
36
|
+
This package only consumes them.
|
|
37
|
+
- **Does not run any web server or dashboard.** Live dashboards live in
|
|
38
|
+
example apps. This package is a CLI plus library plus action.
|
|
39
|
+
- **Does not infer contract schemas from payload samples.** Field-path drift
|
|
40
|
+
is set-difference on dotted paths. Type/value drift is checked only against
|
|
41
|
+
declared schema constraints, not inferred contracts.
|
|
42
|
+
- **Does not modify catalog files outside the stamp markers.** Everything
|
|
43
|
+
the `stamp` command writes is between `<!-- autotel:stamp-start -->`
|
|
44
|
+
and `<!-- autotel:stamp-end -->`. Outside those markers is yours.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm add -D autotel-eventcatalog
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Requires that your services use autotel and `ArchitectureSnapshotSubscriber`
|
|
53
|
+
from `autotel-subscribers/architecture-snapshot` to produce a snapshot.
|
|
54
|
+
|
|
55
|
+
## Use
|
|
56
|
+
|
|
57
|
+
### From the CLI
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
autotel-eventcatalog drift \
|
|
61
|
+
--snapshot ./services/test/snapshot.json \
|
|
62
|
+
--catalog ./catalog \
|
|
63
|
+
--output ./drift.md \
|
|
64
|
+
--summary-output ./drift-summary.json \
|
|
65
|
+
--policy all \
|
|
66
|
+
--fail-on-drift # exit 1 on drift; wire this into CI
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### From code
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {
|
|
73
|
+
loadSnapshot,
|
|
74
|
+
readCatalogState,
|
|
75
|
+
diffCatalogAgainstSnapshot,
|
|
76
|
+
renderMarkdown,
|
|
77
|
+
renderTerminal, // plain-text, for Slack / log files / non-MD terminals
|
|
78
|
+
renderJson, // versioned envelope for downstream tooling
|
|
79
|
+
countDriftReport, // per-category counts that match the dashboard hero badge
|
|
80
|
+
hasDrift,
|
|
81
|
+
stampCatalog, // write runtime evidence into your catalog's event mdx
|
|
82
|
+
buildStampSummary, // count of inserts / replaces / no-ops / skipped
|
|
83
|
+
} from 'autotel-eventcatalog';
|
|
84
|
+
|
|
85
|
+
const snapshot = await loadSnapshot('./services/test/snapshot.json');
|
|
86
|
+
const catalog = await readCatalogState('./catalog');
|
|
87
|
+
const report = diffCatalogAgainstSnapshot(snapshot, catalog);
|
|
88
|
+
|
|
89
|
+
console.log(renderMarkdown(report));
|
|
90
|
+
console.log('findings:', countDriftReport(report).total);
|
|
91
|
+
if (hasDrift(report)) process.exit(1);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### What a run actually prints
|
|
95
|
+
|
|
96
|
+
The example app in this monorepo (`apps/example-eventcatalog`) ships with
|
|
97
|
+
two deliberate drift conditions so the CLI has something real to find.
|
|
98
|
+
Running `pnpm catalog:drift` there prints:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
# Architecture drift report
|
|
102
|
+
|
|
103
|
+
## Events documented but never observed
|
|
104
|
+
|
|
105
|
+
- `PaymentFailed`
|
|
106
|
+
|
|
107
|
+
## Field-path drift
|
|
108
|
+
|
|
109
|
+
### `recommendation.generated`
|
|
110
|
+
|
|
111
|
+
**Extra fields in payloads (not in declared schema):**
|
|
112
|
+
|
|
113
|
+
- `personalization_seed`
|
|
114
|
+
|
|
115
|
+
Drift detected in current snapshot.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Exit code 1, CI fails. Two genuine findings, zero noise:
|
|
119
|
+
|
|
120
|
+
- **`PaymentFailed` documented but never observed** — the failure path
|
|
121
|
+
isn't exercised by the integration tests. A real coverage gap.
|
|
122
|
+
- **`personalization_seed` extra field** — the service emits it; the
|
|
123
|
+
catalog schema doesn't declare it. Real schema drift.
|
|
124
|
+
|
|
125
|
+
Type drift handles the JSON Schema ↔ JavaScript impedance mismatch
|
|
126
|
+
deliberately: a declared `integer` accepts an observed `number` at the
|
|
127
|
+
type level, then sample values are checked against `Number.isInteger`
|
|
128
|
+
so a runtime `1.5` against a declared `integer` still flags. No false
|
|
129
|
+
positives, no swept-under-the-rug genuine signal.
|
|
130
|
+
|
|
131
|
+
### As a GitHub Action
|
|
132
|
+
|
|
133
|
+
The package ships a composite action so any repository can wire drift checking
|
|
134
|
+
into its PR pipeline with a single step:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
# .github/workflows/eventcatalog-drift.yml
|
|
138
|
+
name: eventcatalog drift
|
|
139
|
+
on:
|
|
140
|
+
pull_request:
|
|
141
|
+
branches: [main]
|
|
142
|
+
jobs:
|
|
143
|
+
drift:
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
permissions:
|
|
146
|
+
contents: read
|
|
147
|
+
pull-requests: write
|
|
148
|
+
steps:
|
|
149
|
+
- uses: actions/checkout@v4
|
|
150
|
+
with: { fetch-depth: 0 } # so the action can read the base branch
|
|
151
|
+
|
|
152
|
+
# Produce the snapshot however you like; typically by running your
|
|
153
|
+
# integration tests with ArchitectureSnapshotSubscriber wired in.
|
|
154
|
+
- run: pnpm install --frozen-lockfile
|
|
155
|
+
- run: pnpm services:snapshot
|
|
156
|
+
|
|
157
|
+
- uses: jagreehal/autotel-eventcatalog@v0
|
|
158
|
+
with:
|
|
159
|
+
snapshot: ./services/test/snapshot.json
|
|
160
|
+
catalog: ./catalog
|
|
161
|
+
base-ref: origin/${{ github.base_ref }} # compare to PR base
|
|
162
|
+
fail-on-drift: true # fail the PR on new drift
|
|
163
|
+
comment-on-pr: true # post a sticky comment
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
CLI policy modes:
|
|
167
|
+
|
|
168
|
+
- `--policy all`: fail on any drift in the current snapshot.
|
|
169
|
+
- `--policy new-only`: fail only on drift introduced vs `--base-snapshot`.
|
|
170
|
+
|
|
171
|
+
What lands on the PR:
|
|
172
|
+
|
|
173
|
+
- A sticky comment titled "Architecture drift: what this change introduces"
|
|
174
|
+
with sections for new events, removed events, field-path drift, type drift,
|
|
175
|
+
value drift, and drift the PR resolved.
|
|
176
|
+
- The check fails only when this PR introduces _new_ drift. Pre-existing
|
|
177
|
+
drift is reported for context but does not block.
|
|
178
|
+
- The drift report is also written to `$RUNNER_TEMP/autotel-eventcatalog-drift.md`
|
|
179
|
+
and printed in the job log.
|
|
180
|
+
|
|
181
|
+
## What gets caught
|
|
182
|
+
|
|
183
|
+
| Drift class | Example finding |
|
|
184
|
+
| ---------------------------------------- | ---------------------------------------------------------- |
|
|
185
|
+
| **Events observed but undocumented** | `order.cancelled` emitted by code; no entry in catalog |
|
|
186
|
+
| **Events documented but never observed** | `LegacyEvent` in catalog; never seen in tests |
|
|
187
|
+
| **Field-path drift (extra)** | `personalization_seed` in payload; not declared in schema |
|
|
188
|
+
| **Field-path drift (missing)** | `customerId` declared in schema; never present in payloads |
|
|
189
|
+
| **Type drift** | `amount` declared `number`; observed `string` |
|
|
190
|
+
| **Value drift (enum mismatch)** | `status: "placed"` observed; schema enum excludes it |
|
|
191
|
+
| **Services observed but undocumented** | `OrdersService` is a producer; no service page |
|
|
192
|
+
| **Channels observed but undocumented** | `orders.events` carries messages; no channel page |
|
|
193
|
+
|
|
194
|
+
**How type and value drift work in practice.**
|
|
195
|
+
|
|
196
|
+
1. `ArchitectureSnapshotSubscriber` records `fieldStats` per event during
|
|
197
|
+
a test run — for each dotted path it captures the runtime types it
|
|
198
|
+
saw (`string`, `number`, `object`, …) and up to 20 primitive sample
|
|
199
|
+
values. Verified by
|
|
200
|
+
[`snapshot-fieldstats.integration.test.ts`](../../apps/example-eventcatalog/services/test/snapshot-fieldstats.integration.test.ts),
|
|
201
|
+
which runs the real `placeOrder` → `handleOrderPlaced` →
|
|
202
|
+
`handlePaymentCaptured` → `generateRecommendation` flow and asserts
|
|
203
|
+
`fieldStats.totalCents.types` contains `"number"`,
|
|
204
|
+
`fieldStats.currency.sampleValues` contains `"GBP"`, etc.
|
|
205
|
+
2. `readCatalogState` parses each event's JSON Schema (via `schemaPath`
|
|
206
|
+
in the catalog frontmatter) into a map of declared `types` and `enum`
|
|
207
|
+
values per path.
|
|
208
|
+
3. `diffCatalogAgainstSnapshot` cross-joins the two: any path whose
|
|
209
|
+
observed runtime type is not in the declared types becomes a
|
|
210
|
+
`typeDrift` entry; any sample value not in the declared enum becomes
|
|
211
|
+
a `valueDrift` entry. Both feed `countDriftReport` and the markdown
|
|
212
|
+
renderer.
|
|
213
|
+
|
|
214
|
+
A planned next step is first-class Zod metadata at `track()` call sites
|
|
215
|
+
so contracts can be declared in code rather than only in catalog schemas.
|
|
216
|
+
|
|
217
|
+
## Public JSON contract
|
|
218
|
+
|
|
219
|
+
Three versioned JSON shapes are shipped with the package as JSON Schema
|
|
220
|
+
files (`schemas/` directory). Downstream tooling (your own GitHub Actions,
|
|
221
|
+
dashboards, Slack bots) should validate against these:
|
|
222
|
+
|
|
223
|
+
| Schema | Emitted by | Consumed by |
|
|
224
|
+
| ----------------------------------- | ---------------------------- | ---------------------------------------- |
|
|
225
|
+
| `schemas/drift-report-v0.1.0.json` | `drift --format json` | Any downstream parser |
|
|
226
|
+
| `schemas/drift-summary-v0.1.0.json` | `drift --summary-output ...` | CI gating, dashboards |
|
|
227
|
+
| `schemas/stamp-summary-v0.1.0.json` | `stamp --summary-output ...` | "Did this PR forget to re-stamp?" checks |
|
|
228
|
+
|
|
229
|
+
Every envelope carries a `spec: 'autotel-eventcatalog-…/vX.Y.Z'` field
|
|
230
|
+
that downstream code can use to refuse unknown major versions. The shapes
|
|
231
|
+
are locked by golden contract tests inside this package: a PR that
|
|
232
|
+
changes them without bumping the spec version will fail CI.
|
|
233
|
+
|
|
234
|
+
## Renderers (advanced)
|
|
235
|
+
|
|
236
|
+
`drift --format <name>` dispatches through a small renderer registry. The
|
|
237
|
+
built-ins are `markdown`, `terminal`, `json`. The registry is exported
|
|
238
|
+
from the library so applications and other tooling can add their own:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { RENDERERS, type Renderer } from 'autotel-eventcatalog';
|
|
242
|
+
|
|
243
|
+
const sarifRenderer: Renderer = {
|
|
244
|
+
name: 'sarif',
|
|
245
|
+
description: 'Static Analysis Results Interchange Format',
|
|
246
|
+
renderReport(report) {
|
|
247
|
+
/* ... */ return '';
|
|
248
|
+
},
|
|
249
|
+
renderDelta(delta) {
|
|
250
|
+
/* ... */ return '';
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
// Programmatic consumers can plug it in. The CLI is also extensible via
|
|
254
|
+
// a follow-up release that supports loading renderers from a config file.
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The core diff and policy modules are renderer-agnostic; adding a new
|
|
258
|
+
output target should never require touching `diff.ts` or `policy.ts`.
|
|
259
|
+
|
|
260
|
+
## What writes to the catalog
|
|
261
|
+
|
|
262
|
+
`drift` is **read-only**. It diffs the snapshot against the catalog and
|
|
263
|
+
reports findings. It never modifies catalog files.
|
|
264
|
+
|
|
265
|
+
`stamp` is the **write path**. By design, it injects a runtime evidence
|
|
266
|
+
block into each event mdx between idempotent markers
|
|
267
|
+
(`<!-- autotel:stamp-start -->` / `<!-- autotel:stamp-end -->`). Re-runs
|
|
268
|
+
replace the content between the markers; nothing outside the markers is
|
|
269
|
+
touched. Run with `--dry-run` to preview the plan first.
|
|
270
|
+
|
|
271
|
+
Pass `--summary-output stamp.json` to write a versioned summary file
|
|
272
|
+
(`autotel-eventcatalog-stamp-summary/v0.1.0`) describing how many files
|
|
273
|
+
were inserted, replaced, no-op'd, or skipped. Useful for CI checks that
|
|
274
|
+
need to gate on "did this PR forget to re-stamp?".
|
|
275
|
+
|
|
276
|
+
## Working example
|
|
277
|
+
|
|
278
|
+
See [`apps/example-eventcatalog`](../../apps/example-eventcatalog) in the
|
|
279
|
+
monorepo. It has a working e-commerce catalog and a snapshot. Running
|
|
280
|
+
`pnpm catalog:drift` from that app prints a drift report that surfaces
|
|
281
|
+
two findings, including a deliberately-introduced `personalization_seed`
|
|
282
|
+
field that the schema does not declare.
|
|
283
|
+
|
|
284
|
+
## Documentation
|
|
285
|
+
|
|
286
|
+
| If you want to… | Read |
|
|
287
|
+
| ------------------------------------------------------ | ---------------------------------------------------- |
|
|
288
|
+
| Consume the JSON output from your own tool | [`docs/CONTRACT.md`](docs/CONTRACT.md) |
|
|
289
|
+
| Write a custom renderer (SARIF, Slack, your dashboard) | [`docs/EXTENDING.md`](docs/EXTENDING.md) |
|
|
290
|
+
| Debug a CLI exit code or a CI failure | [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) |
|
|
291
|
+
| Upgrade between versions safely | [`docs/UPGRADING.md`](docs/UPGRADING.md) |
|
|
292
|
+
| See what shipped in each release | [`CHANGELOG.md`](CHANGELOG.md) |
|
|
293
|
+
| Contribute to the package | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
|
|
294
|
+
| Validate the published JSON shapes | [`schemas/README.md`](schemas/README.md) |
|
|
295
|
+
|
|
296
|
+
## Used by
|
|
297
|
+
|
|
298
|
+
_If you adopt this in production, open a PR adding your team to this
|
|
299
|
+
section. We'd like to know who depends on the contract before shipping
|
|
300
|
+
breaking changes, and seeing a name here helps future adopters gauge
|
|
301
|
+
the project's maturity._
|
|
302
|
+
|
|
303
|
+
- _no public adopters yet; be the first_
|
|
304
|
+
|
|
305
|
+
## License
|
|
306
|
+
|
|
307
|
+
MIT.
|
package/action.yml
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
name: 'autotel-eventcatalog drift'
|
|
2
|
+
description: |
|
|
3
|
+
Diff your autotel architecture snapshot against an EventCatalog and surface
|
|
4
|
+
drift on the pull request. With base-ref input, only drift the PR introduces
|
|
5
|
+
fails the check — pre-existing drift is reported but ignored.
|
|
6
|
+
|
|
7
|
+
branding:
|
|
8
|
+
icon: 'activity'
|
|
9
|
+
color: 'orange'
|
|
10
|
+
|
|
11
|
+
inputs:
|
|
12
|
+
snapshot:
|
|
13
|
+
description: 'Path to the architecture snapshot JSON (the PR head version).'
|
|
14
|
+
required: true
|
|
15
|
+
catalog:
|
|
16
|
+
description: 'Path to the EventCatalog directory (containing eventcatalog.config.* and domains/).'
|
|
17
|
+
required: true
|
|
18
|
+
base-ref:
|
|
19
|
+
description: |
|
|
20
|
+
Git ref to read the baseline snapshot from (typically the PR base branch,
|
|
21
|
+
e.g. `origin/main`). When set, the action reports only drift the PR
|
|
22
|
+
introduces — pre-existing drift is shown for context but does not fail.
|
|
23
|
+
required: false
|
|
24
|
+
default: ''
|
|
25
|
+
fail-on-drift:
|
|
26
|
+
description: 'Fail the check when drift is detected. With base-ref, fails only on *new* drift.'
|
|
27
|
+
required: false
|
|
28
|
+
default: 'true'
|
|
29
|
+
comment-on-pr:
|
|
30
|
+
description: 'Post the drift report as a sticky comment on the pull request.'
|
|
31
|
+
required: false
|
|
32
|
+
default: 'true'
|
|
33
|
+
comment-header:
|
|
34
|
+
description: 'Header tag for the sticky comment (use this if you have multiple drift checks).'
|
|
35
|
+
required: false
|
|
36
|
+
default: 'autotel-eventcatalog'
|
|
37
|
+
package-version:
|
|
38
|
+
description: |
|
|
39
|
+
Semver range for autotel-eventcatalog. Defaults to `^0` so a new
|
|
40
|
+
major release won't silently land on your CI — pin to an exact
|
|
41
|
+
version (e.g. `0.1.0`) for fully reproducible runs.
|
|
42
|
+
required: false
|
|
43
|
+
default: '^0'
|
|
44
|
+
continue-on-comment-failure:
|
|
45
|
+
description: |
|
|
46
|
+
When `true`, a failure of the sticky-comment step (e.g. GitHub API
|
|
47
|
+
rate limit, transient 5xx, permissions) is downgraded to a warning
|
|
48
|
+
and the workflow keeps running. The drift check itself is
|
|
49
|
+
unaffected. Default `true` because a missed comment is annoying;
|
|
50
|
+
a failed job because of a missed comment is worse.
|
|
51
|
+
required: false
|
|
52
|
+
default: 'true'
|
|
53
|
+
|
|
54
|
+
outputs:
|
|
55
|
+
drift-detected:
|
|
56
|
+
description: '"true" if any drift was detected, "false" otherwise.'
|
|
57
|
+
value: ${{ steps.run.outputs.drift-detected }}
|
|
58
|
+
report-path:
|
|
59
|
+
description: 'Path to the generated markdown drift report.'
|
|
60
|
+
value: ${{ steps.run.outputs.report-path }}
|
|
61
|
+
|
|
62
|
+
runs:
|
|
63
|
+
using: composite
|
|
64
|
+
steps:
|
|
65
|
+
- name: Resolve base snapshot
|
|
66
|
+
id: base
|
|
67
|
+
if: ${{ inputs.base-ref != '' }}
|
|
68
|
+
shell: bash
|
|
69
|
+
run: |
|
|
70
|
+
set -euo pipefail
|
|
71
|
+
BASE_SNAPSHOT="$RUNNER_TEMP/autotel-eventcatalog-base-snapshot.json"
|
|
72
|
+
if git show "${{ inputs.base-ref }}:${{ inputs.snapshot }}" > "$BASE_SNAPSHOT" 2>/dev/null; then
|
|
73
|
+
echo "Found base snapshot at ${{ inputs.base-ref }}:${{ inputs.snapshot }}"
|
|
74
|
+
echo "path=$BASE_SNAPSHOT" >> "$GITHUB_OUTPUT"
|
|
75
|
+
else
|
|
76
|
+
echo "::notice::No base snapshot at ${{ inputs.base-ref }}:${{ inputs.snapshot }} — first run on this branch?"
|
|
77
|
+
echo "path=" >> "$GITHUB_OUTPUT"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
- name: Run drift check
|
|
81
|
+
id: run
|
|
82
|
+
shell: bash
|
|
83
|
+
env:
|
|
84
|
+
SNAPSHOT: ${{ inputs.snapshot }}
|
|
85
|
+
CATALOG: ${{ inputs.catalog }}
|
|
86
|
+
BASE_SNAPSHOT_PATH: ${{ steps.base.outputs.path }}
|
|
87
|
+
PACKAGE_VERSION: ${{ inputs.package-version }}
|
|
88
|
+
run: |
|
|
89
|
+
set -euo pipefail
|
|
90
|
+
REPORT="$RUNNER_TEMP/autotel-eventcatalog-drift.md"
|
|
91
|
+
SUMMARY="$RUNNER_TEMP/autotel-eventcatalog-summary.json"
|
|
92
|
+
echo "report-path=$REPORT" >> "$GITHUB_OUTPUT"
|
|
93
|
+
|
|
94
|
+
# Always pass --fail-on-drift internally so the exit code is the
|
|
95
|
+
# authoritative drift signal. The user-facing `fail-on-drift` input
|
|
96
|
+
# only controls whether we re-emit that failure as a workflow
|
|
97
|
+
# failure further down — the action itself needs the signal either
|
|
98
|
+
# way (for the drift-detected output, the sticky comment, etc.).
|
|
99
|
+
POLICY="all"
|
|
100
|
+
ARGS=(--snapshot "$SNAPSHOT" --catalog "$CATALOG" --output "$REPORT" --summary-output "$SUMMARY" --fail-on-drift)
|
|
101
|
+
if [ -n "${BASE_SNAPSHOT_PATH:-}" ]; then
|
|
102
|
+
ARGS+=(--base-snapshot "$BASE_SNAPSHOT_PATH")
|
|
103
|
+
POLICY="new-only"
|
|
104
|
+
fi
|
|
105
|
+
ARGS+=(--policy "$POLICY")
|
|
106
|
+
|
|
107
|
+
# Run the CLI — non-zero exit means drift was detected.
|
|
108
|
+
set +e
|
|
109
|
+
npx --yes "autotel-eventcatalog@$PACKAGE_VERSION" drift "${ARGS[@]}"
|
|
110
|
+
STATUS=$?
|
|
111
|
+
set -e
|
|
112
|
+
|
|
113
|
+
if [ $STATUS -ne 0 ] && [ $STATUS -ne 1 ]; then
|
|
114
|
+
# Anything else (2 = bad args, etc.) is a hard error; surface it.
|
|
115
|
+
echo "::error::autotel-eventcatalog drift CLI failed with exit $STATUS"
|
|
116
|
+
exit $STATUS
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [ ! -f "$SUMMARY" ]; then
|
|
120
|
+
echo "::error::autotel-eventcatalog did not produce a summary output."
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
DRIFT_DETECTED="$(node -e "const fs=require('fs');const s=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(s.shouldFail ? 'true':'false')" "$SUMMARY")"
|
|
124
|
+
echo "drift-detected=$DRIFT_DETECTED" >> "$GITHUB_OUTPUT"
|
|
125
|
+
|
|
126
|
+
# Always show the report in the job log for easy review.
|
|
127
|
+
echo "::group::Drift report"
|
|
128
|
+
cat "$REPORT"
|
|
129
|
+
echo "::endgroup::"
|
|
130
|
+
|
|
131
|
+
# Sticky-comment action pinned to a specific commit SHA. This is a
|
|
132
|
+
# supply-chain control — a future malicious tag wouldn't affect anyone
|
|
133
|
+
# using this action. Update the SHA deliberately when bumping the
|
|
134
|
+
# external action's version. Currently pinned to v2.9.4 (2024-12-03).
|
|
135
|
+
- name: Post sticky PR comment
|
|
136
|
+
id: comment
|
|
137
|
+
if: ${{ inputs.comment-on-pr == 'true' && github.event_name == 'pull_request' }}
|
|
138
|
+
continue-on-error: ${{ inputs.continue-on-comment-failure == 'true' }}
|
|
139
|
+
uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728
|
|
140
|
+
with:
|
|
141
|
+
header: ${{ inputs.comment-header }}
|
|
142
|
+
path: ${{ steps.run.outputs.report-path }}
|
|
143
|
+
|
|
144
|
+
- name: Warn on comment failure
|
|
145
|
+
if: ${{ steps.comment.outcome == 'failure' }}
|
|
146
|
+
shell: bash
|
|
147
|
+
run: |
|
|
148
|
+
echo "::warning::Could not post the drift report as a PR comment. The drift check itself completed and the report is available at ${{ steps.run.outputs.report-path }}."
|
|
149
|
+
|
|
150
|
+
- name: Fail on drift
|
|
151
|
+
if: ${{ inputs.fail-on-drift == 'true' && steps.run.outputs.drift-detected == 'true' }}
|
|
152
|
+
shell: bash
|
|
153
|
+
run: |
|
|
154
|
+
echo "::error::autotel-eventcatalog detected drift between the catalog and the snapshot."
|
|
155
|
+
exit 1
|