autotel-pact 7.0.5 → 9.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/README.md +37 -40
- package/dist/attrs-DbCs1ou0.cjs.map +1 -1
- package/dist/attrs-ElwK54Ym.js.map +1 -1
- package/dist/audit.cjs.map +1 -1
- package/dist/audit.d.cts +2 -3
- package/dist/audit.d.cts.map +1 -1
- package/dist/audit.d.ts +2 -3
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/auto-wrap.cjs.map +1 -1
- package/dist/auto-wrap.d.cts.map +1 -1
- package/dist/auto-wrap.d.ts.map +1 -1
- package/dist/auto-wrap.js.map +1 -1
- package/dist/broker.cjs.map +1 -1
- package/dist/broker.d.cts +1 -2
- package/dist/broker.d.cts.map +1 -1
- package/dist/broker.d.ts +1 -2
- package/dist/broker.d.ts.map +1 -1
- package/dist/broker.js.map +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/labels-0Cwk5E-8.cjs.map +1 -1
- package/dist/labels-BQjT_Zrv.js.map +1 -1
- package/dist/ledger-BayQwemz.cjs.map +1 -1
- package/dist/{ledger-Bzh_6tbV.d.ts → ledger-CBUbpQr-.d.ts} +2 -3
- package/dist/ledger-CBUbpQr-.d.ts.map +1 -0
- package/dist/ledger-sII3Ig3Y.js.map +1 -1
- package/dist/{ledger-BAFsRX6y.d.cts → ledger-yzVOA9iE.d.cts} +2 -3
- package/dist/ledger-yzVOA9iE.d.cts.map +1 -0
- package/dist/processor.cjs.map +1 -1
- package/dist/processor.d.cts +1 -2
- package/dist/processor.d.cts.map +1 -1
- package/dist/processor.d.ts +1 -2
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js.map +1 -1
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.cts +1 -2
- package/dist/provider.d.cts.map +1 -1
- package/dist/provider.d.ts +1 -2
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js.map +1 -1
- package/dist/tag.d.cts +1 -2
- package/dist/tag.d.cts.map +1 -1
- package/dist/tag.d.ts +1 -2
- package/dist/tag.d.ts.map +1 -1
- package/dist/{types-D_0kgK2e.d.ts → types-DpHqSzxb.d.cts} +11 -6
- package/dist/types-DpHqSzxb.d.cts.map +1 -0
- package/dist/{types-D_0kgK2e.d.cts → types-DpHqSzxb.d.ts} +11 -6
- package/dist/types-DpHqSzxb.d.ts.map +1 -0
- package/package.json +5 -5
- package/dist/ledger-BAFsRX6y.d.cts.map +0 -1
- package/dist/ledger-Bzh_6tbV.d.ts.map +0 -1
- package/dist/types-D_0kgK2e.d.cts.map +0 -1
- package/dist/types-D_0kgK2e.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -14,12 +14,12 @@ A green Pact suite is evidence of **compatibility**. An observed verified contra
|
|
|
14
14
|
|
|
15
15
|
Every feature states what was observed or verified. We never claim precision the data cannot support.
|
|
16
16
|
|
|
17
|
-
| Evidence
|
|
18
|
-
|
|
19
|
-
| **Seen in test**
|
|
20
|
-
| **Seen in production** | A span tagged with `pact.*` was recorded at runtime
|
|
21
|
-
| **Provider verified**
|
|
22
|
-
| **Broker verified**
|
|
17
|
+
| Evidence | What it means | What you must configure |
|
|
18
|
+
| ---------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
19
|
+
| **Seen in test** | Consumer exercised the interaction in CI (`source=test`, `role=consumer`) | `withPactInteraction` or `auto-wrap` |
|
|
20
|
+
| **Seen in production** | A span tagged with `pact.*` was recorded at runtime | `tagPactInteraction()` + `PactLedgerSpanProcessor` |
|
|
21
|
+
| **Provider verified** | Provider `verifyProvider()` succeeded and we enumerated interactions from the pact file | `withProviderVerification` |
|
|
22
|
+
| **Broker verified** | Latest broker verification for the consumer–provider **pact pair** succeeded | Broker URL/token at **audit** time |
|
|
23
23
|
|
|
24
24
|
> **Broker limitation:** Broker verification proves the latest pact between a consumer and provider passed. It does **not** prove autotel-pact observed each interaction.
|
|
25
25
|
|
|
@@ -218,19 +218,16 @@ The wrapper parses the pact files, fans out one ledger row per interaction with
|
|
|
218
218
|
Tag business spans, then register the processor:
|
|
219
219
|
|
|
220
220
|
```ts
|
|
221
|
-
import { init } from 'autotel';
|
|
222
|
-
import { trace } from 'autotel';
|
|
221
|
+
import { init, span } from 'autotel';
|
|
223
222
|
import { createPactLedgerProcessor } from 'autotel-pact/processor';
|
|
224
223
|
import { tagPactInteraction } from 'autotel-pact/tag';
|
|
225
224
|
|
|
226
225
|
init({
|
|
227
226
|
service: 'order-service',
|
|
228
|
-
spanProcessors: [
|
|
229
|
-
createPactLedgerProcessor({ dir: '.autotel-pact-prod' }),
|
|
230
|
-
],
|
|
227
|
+
spanProcessors: [createPactLedgerProcessor({ dir: '.autotel-pact-prod' })],
|
|
231
228
|
});
|
|
232
229
|
|
|
233
|
-
|
|
230
|
+
span('handleOrderCreated', () => {
|
|
234
231
|
tagPactInteraction({
|
|
235
232
|
consumer: 'OrderShipper',
|
|
236
233
|
provider: 'OrderService',
|
|
@@ -280,26 +277,26 @@ STATUS CONTRACTED TEST_SEEN PROD_SEEN PROVIDER_VERIFIED BROKER_VERIFIED CO
|
|
|
280
277
|
|
|
281
278
|
### Status meanings
|
|
282
279
|
|
|
283
|
-
| Status | Contracted in `pacts/` | In ledger window | Meaning
|
|
284
|
-
|
|
285
|
-
| OK | yes | yes | Trusted path, exercised this window
|
|
286
|
-
| STALE | yes | no | Pact exists but no test exercised it
|
|
287
|
-
| SHADOW | no | yes | A wrapped call ran with no matching contract
|
|
280
|
+
| Status | Contracted in `pacts/` | In ledger window | Meaning |
|
|
281
|
+
| ------ | ---------------------- | ---------------- | -------------------------------------------- |
|
|
282
|
+
| OK | yes | yes | Trusted path, exercised this window |
|
|
283
|
+
| STALE | yes | no | Pact exists but no test exercised it |
|
|
284
|
+
| SHADOW | no | yes | A wrapped call ran with no matching contract |
|
|
288
285
|
|
|
289
286
|
### Flags
|
|
290
287
|
|
|
291
|
-
| Flag
|
|
292
|
-
|
|
293
|
-
| `--pacts <dir>`
|
|
294
|
-
| `--ledger <dir>`
|
|
295
|
-
| `--window <days>`
|
|
296
|
-
| `--gate`
|
|
297
|
-
| `--gate=strict`
|
|
298
|
-
| `--gate=broker`
|
|
299
|
-
| `--broker-url`
|
|
300
|
-
| `--broker-token`
|
|
301
|
-
| `--json`
|
|
302
|
-
| `--help`
|
|
288
|
+
| Flag | Default | Description |
|
|
289
|
+
| ----------------- | --------------- | --------------------------------------------------------------------- |
|
|
290
|
+
| `--pacts <dir>` | `./pacts` | Directory containing pact files |
|
|
291
|
+
| `--ledger <dir>` | `.autotel-pact` | Directory containing ledger files |
|
|
292
|
+
| `--window <days>` | `14` | Ledger lookback window |
|
|
293
|
+
| `--gate` | off | Exit 1 if any contracted interaction was not **seen in test** |
|
|
294
|
+
| `--gate=strict` | off | Also exit 1 on observations with no matching contract |
|
|
295
|
+
| `--gate=broker` | off | Exit 1 if broker configured and any contracted row lacks broker proof |
|
|
296
|
+
| `--broker-url` | env | Pact Broker base URL (`PACT_BROKER_BASE_URL`) |
|
|
297
|
+
| `--broker-token` | env | Bearer token (`PACT_BROKER_TOKEN`) |
|
|
298
|
+
| `--json` | off | Machine-readable JSON (`test_seen`, `prod_seen`, …) |
|
|
299
|
+
| `--help` | | Show help |
|
|
303
300
|
|
|
304
301
|
### CI gating
|
|
305
302
|
|
|
@@ -323,7 +320,7 @@ A few things to know before you turn `--gate` on for the whole org.
|
|
|
323
320
|
|
|
324
321
|
A STALE row does **not** mean "never fires in production". It means no consumer test exercised this interaction in the window. Check **PROD_SEEN** for production evidence (requires explicit span tags + processor).
|
|
325
322
|
|
|
326
|
-
Honest one-liner:
|
|
323
|
+
Honest one-liner: _"verified by Pact, exercised by our tests, observed in production only when we tag spans."_
|
|
327
324
|
|
|
328
325
|
### Coverage equals wrapped tests
|
|
329
326
|
|
|
@@ -351,21 +348,21 @@ A contract that should not run anymore is a contract you should delete, not gate
|
|
|
351
348
|
|
|
352
349
|
## Attribute schema
|
|
353
350
|
|
|
354
|
-
| Attribute
|
|
355
|
-
|
|
356
|
-
| `pact.consumer`
|
|
357
|
-
| `pact.provider`
|
|
358
|
-
| `pact.kind`
|
|
359
|
-
| `pact.interaction.description`
|
|
360
|
-
| `pact.interaction.states`
|
|
361
|
-
| `pact.contract.file`
|
|
362
|
-
| `pact.outcome`
|
|
351
|
+
| Attribute | Type | Description |
|
|
352
|
+
| ------------------------------ | -------- | ---------------------------------------------- |
|
|
353
|
+
| `pact.consumer` | string | Consumer name from Pact config |
|
|
354
|
+
| `pact.provider` | string | Provider name from Pact config |
|
|
355
|
+
| `pact.kind` | string | `"message"` (v1) or `"http"` (v2) |
|
|
356
|
+
| `pact.interaction.description` | string | The `expectsToReceive` or `uponReceiving` text |
|
|
357
|
+
| `pact.interaction.states` | string[] | Provider state names from `.given()` |
|
|
358
|
+
| `pact.contract.file` | string? | Path to the pact file (when supplied) |
|
|
359
|
+
| `pact.outcome` | string | `"passed"` or `"failed"` |
|
|
363
360
|
|
|
364
361
|
The `pact.*` namespace is currently unclaimed in OTel semantic conventions; we plan to propose it upstream once the package has traction. Pin to a specific `autotel-pact` version if you build dashboards against these attributes.
|
|
365
362
|
|
|
366
363
|
## How this is different from…
|
|
367
364
|
|
|
368
|
-
- **Pact's own `--enable-otel`** flags emit telemetry about the
|
|
365
|
+
- **Pact's own `--enable-otel`** flags emit telemetry about the _Pact tooling itself_ (broker calls, verifier execution). `autotel-pact` operates at the **interaction wrapper layer in your code**, capturing whether the contract interactions ran.
|
|
369
366
|
- **Tracetest** lets you write tests that assert against traces. `autotel-pact` does not change how you write tests. It audits which of your existing Pact contracts have evidence of being exercised.
|
|
370
367
|
- **Pact Broker / PactFlow `can-i-deploy`** verifies version compatibility. `autotel-pact` adds the orthogonal question: have these verified contracts run recently?
|
|
371
368
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attrs-DbCs1ou0.cjs","names":[],"sources":["../src/attrs.ts"],"sourcesContent":["import type { PactInteractionMeta, PactOutcome } from './types.js';\n\n/**\n * Attribute keys for Pact interactions. Centralised so the namespace is\n * a single source of truth and is forward-compatible with eventual OTel\n * semantic conventions.\n */\nexport const PACT_ATTRS = {\n CONSUMER: 'pact.consumer',\n PROVIDER: 'pact.provider',\n KIND: 'pact.kind',\n INTERACTION_DESCRIPTION: 'pact.interaction.description',\n INTERACTION_ID: 'pact.interaction.id',\n INTERACTION_STATES: 'pact.interaction.states',\n CONTRACT_FILE: 'pact.contract.file',\n OUTCOME: 'pact.outcome',\n} as const;\n\nexport type PactAttributeKey = (typeof PACT_ATTRS)[keyof typeof PACT_ATTRS];\n\n/**\n * Build the set of attributes to stamp on a span when an interaction is\n * about to be exercised. `outcome` is added later by the wrapper.\n */\nexport function buildPactAttributes(\n meta: PactInteractionMeta,\n opts: { contractFile?: string } = {},\n): Record<string, string | string[]> {\n const attrs: Record<string, string | string[]> = {\n [PACT_ATTRS.CONSUMER]: meta.consumer,\n [PACT_ATTRS.PROVIDER]: meta.provider,\n [PACT_ATTRS.KIND]: meta.kind,\n [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,\n [PACT_ATTRS.INTERACTION_STATES]: meta.states,\n };\n if (opts.contractFile) {\n attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;\n }\n if (meta.interactionId) {\n attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;\n }\n return attrs;\n}\n\n/**\n * Helper that returns just the outcome attribute — stamped after the\n * handler resolves or rejects.\n */\nexport function outcomeAttribute(
|
|
1
|
+
{"version":3,"file":"attrs-DbCs1ou0.cjs","names":[],"sources":["../src/attrs.ts"],"sourcesContent":["import type { PactInteractionMeta, PactOutcome } from './types.js';\n\n/**\n * Attribute keys for Pact interactions. Centralised so the namespace is\n * a single source of truth and is forward-compatible with eventual OTel\n * semantic conventions.\n */\nexport const PACT_ATTRS = {\n CONSUMER: 'pact.consumer',\n PROVIDER: 'pact.provider',\n KIND: 'pact.kind',\n INTERACTION_DESCRIPTION: 'pact.interaction.description',\n INTERACTION_ID: 'pact.interaction.id',\n INTERACTION_STATES: 'pact.interaction.states',\n CONTRACT_FILE: 'pact.contract.file',\n OUTCOME: 'pact.outcome',\n} as const;\n\nexport type PactAttributeKey = (typeof PACT_ATTRS)[keyof typeof PACT_ATTRS];\n\n/**\n * Build the set of attributes to stamp on a span when an interaction is\n * about to be exercised. `outcome` is added later by the wrapper.\n */\nexport function buildPactAttributes(\n meta: PactInteractionMeta,\n opts: { contractFile?: string } = {},\n): Record<string, string | string[]> {\n const attrs: Record<string, string | string[]> = {\n [PACT_ATTRS.CONSUMER]: meta.consumer,\n [PACT_ATTRS.PROVIDER]: meta.provider,\n [PACT_ATTRS.KIND]: meta.kind,\n [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,\n [PACT_ATTRS.INTERACTION_STATES]: meta.states,\n };\n if (opts.contractFile) {\n attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;\n }\n if (meta.interactionId) {\n attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;\n }\n return attrs;\n}\n\n/**\n * Helper that returns just the outcome attribute — stamped after the\n * handler resolves or rejects.\n */\nexport function outcomeAttribute(outcome: PactOutcome): Record<string, string> {\n return { [PACT_ATTRS.OUTCOME]: outcome };\n}\n"],"mappings":";;;;;;;AAOA,MAAa,aAAa;CACxB,UAAU;CACV,UAAU;CACV,MAAM;CACN,yBAAyB;CACzB,gBAAgB;CAChB,oBAAoB;CACpB,eAAe;CACf,SAAS;AACX;;;;;AAQA,SAAgB,oBACd,MACA,OAAkC,CAAC,GACA;CACnC,MAAM,QAA2C;GAC9C,WAAW,WAAW,KAAK;GAC3B,WAAW,WAAW,KAAK;GAC3B,WAAW,OAAO,KAAK;GACvB,WAAW,0BAA0B,KAAK;GAC1C,WAAW,qBAAqB,KAAK;CACxC;CACA,IAAI,KAAK,cACP,MAAM,WAAW,iBAAiB,KAAK;CAEzC,IAAI,KAAK,eACP,MAAM,WAAW,kBAAkB,KAAK;CAE1C,OAAO;AACT;;;;;AAMA,SAAgB,iBAAiB,SAA8C;CAC7E,OAAO,GAAG,WAAW,UAAU,QAAQ;AACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attrs-ElwK54Ym.js","names":[],"sources":["../src/attrs.ts"],"sourcesContent":["import type { PactInteractionMeta, PactOutcome } from './types.js';\n\n/**\n * Attribute keys for Pact interactions. Centralised so the namespace is\n * a single source of truth and is forward-compatible with eventual OTel\n * semantic conventions.\n */\nexport const PACT_ATTRS = {\n CONSUMER: 'pact.consumer',\n PROVIDER: 'pact.provider',\n KIND: 'pact.kind',\n INTERACTION_DESCRIPTION: 'pact.interaction.description',\n INTERACTION_ID: 'pact.interaction.id',\n INTERACTION_STATES: 'pact.interaction.states',\n CONTRACT_FILE: 'pact.contract.file',\n OUTCOME: 'pact.outcome',\n} as const;\n\nexport type PactAttributeKey = (typeof PACT_ATTRS)[keyof typeof PACT_ATTRS];\n\n/**\n * Build the set of attributes to stamp on a span when an interaction is\n * about to be exercised. `outcome` is added later by the wrapper.\n */\nexport function buildPactAttributes(\n meta: PactInteractionMeta,\n opts: { contractFile?: string } = {},\n): Record<string, string | string[]> {\n const attrs: Record<string, string | string[]> = {\n [PACT_ATTRS.CONSUMER]: meta.consumer,\n [PACT_ATTRS.PROVIDER]: meta.provider,\n [PACT_ATTRS.KIND]: meta.kind,\n [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,\n [PACT_ATTRS.INTERACTION_STATES]: meta.states,\n };\n if (opts.contractFile) {\n attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;\n }\n if (meta.interactionId) {\n attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;\n }\n return attrs;\n}\n\n/**\n * Helper that returns just the outcome attribute — stamped after the\n * handler resolves or rejects.\n */\nexport function outcomeAttribute(
|
|
1
|
+
{"version":3,"file":"attrs-ElwK54Ym.js","names":[],"sources":["../src/attrs.ts"],"sourcesContent":["import type { PactInteractionMeta, PactOutcome } from './types.js';\n\n/**\n * Attribute keys for Pact interactions. Centralised so the namespace is\n * a single source of truth and is forward-compatible with eventual OTel\n * semantic conventions.\n */\nexport const PACT_ATTRS = {\n CONSUMER: 'pact.consumer',\n PROVIDER: 'pact.provider',\n KIND: 'pact.kind',\n INTERACTION_DESCRIPTION: 'pact.interaction.description',\n INTERACTION_ID: 'pact.interaction.id',\n INTERACTION_STATES: 'pact.interaction.states',\n CONTRACT_FILE: 'pact.contract.file',\n OUTCOME: 'pact.outcome',\n} as const;\n\nexport type PactAttributeKey = (typeof PACT_ATTRS)[keyof typeof PACT_ATTRS];\n\n/**\n * Build the set of attributes to stamp on a span when an interaction is\n * about to be exercised. `outcome` is added later by the wrapper.\n */\nexport function buildPactAttributes(\n meta: PactInteractionMeta,\n opts: { contractFile?: string } = {},\n): Record<string, string | string[]> {\n const attrs: Record<string, string | string[]> = {\n [PACT_ATTRS.CONSUMER]: meta.consumer,\n [PACT_ATTRS.PROVIDER]: meta.provider,\n [PACT_ATTRS.KIND]: meta.kind,\n [PACT_ATTRS.INTERACTION_DESCRIPTION]: meta.description,\n [PACT_ATTRS.INTERACTION_STATES]: meta.states,\n };\n if (opts.contractFile) {\n attrs[PACT_ATTRS.CONTRACT_FILE] = opts.contractFile;\n }\n if (meta.interactionId) {\n attrs[PACT_ATTRS.INTERACTION_ID] = meta.interactionId;\n }\n return attrs;\n}\n\n/**\n * Helper that returns just the outcome attribute — stamped after the\n * handler resolves or rejects.\n */\nexport function outcomeAttribute(outcome: PactOutcome): Record<string, string> {\n return { [PACT_ATTRS.OUTCOME]: outcome };\n}\n"],"mappings":";;;;;;AAOA,MAAa,aAAa;CACxB,UAAU;CACV,UAAU;CACV,MAAM;CACN,yBAAyB;CACzB,gBAAgB;CAChB,oBAAoB;CACpB,eAAe;CACf,SAAS;AACX;;;;;AAQA,SAAgB,oBACd,MACA,OAAkC,CAAC,GACA;CACnC,MAAM,QAA2C;GAC9C,WAAW,WAAW,KAAK;GAC3B,WAAW,WAAW,KAAK;GAC3B,WAAW,OAAO,KAAK;GACvB,WAAW,0BAA0B,KAAK;GAC1C,WAAW,qBAAqB,KAAK;CACxC;CACA,IAAI,KAAK,cACP,MAAM,WAAW,iBAAiB,KAAK;CAEzC,IAAI,KAAK,eACP,MAAM,WAAW,kBAAkB,KAAK;CAE1C,OAAO;AACT;;;;;AAMA,SAAgB,iBAAiB,SAA8C;CAC7E,OAAO,GAAG,WAAW,UAAU,QAAQ;AACzC"}
|
package/dist/audit.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.cjs","names":["isProviderVerificationRun","isInteractionLedgerEntry","AUDIT_MATRIX_SPEC","path","listPactFiles","parsePactFile","interactionsFromPactFile","readLedger","fetchBrokerVerifications"],"sources":["../src/audit.ts"],"sourcesContent":["import path from 'node:path';\nimport { fetchBrokerVerifications, type BrokerConfig } from './broker.js';\nimport { readLedger, type LedgerOptions } from './ledger.js';\nimport {\n interactionsFromPactFile,\n listPactFiles,\n parsePactFile,\n} from './pact-file.js';\nimport {\n AUDIT_MATRIX_SPEC,\n isInteractionLedgerEntry,\n isProviderVerificationRun,\n type AuditMatrix,\n type AuditRow,\n type BrokerVerification,\n type InteractionLedgerEntry,\n type LedgerRecord,\n type PactInteractionKey,\n type ProviderVerificationRunEntry,\n} from './types.js';\n\nexport interface AuditOptions extends LedgerOptions {\n pactsDir?: string;\n windowDays?: number;\n broker?: BrokerConfig;\n}\n\nconst DEFAULT_PACTS_DIR = './pacts';\nconst DEFAULT_WINDOW_DAYS = 14;\n\nexport function keyOf(k: PactInteractionKey): string {\n const identity = k.interactionId ?? k.interaction;\n return `${k.consumer}::${k.provider}::${k.kind}::${identity}`;\n}\n\nfunction pairKey(consumer: string, provider: string): string {\n return `${consumer}::${provider}`;\n}\n\nfunction inWindow(observedAt: string, cutoff: number): boolean {\n const t = Date.parse(observedAt);\n return Number.isFinite(t) && t >= cutoff;\n}\n\n/**\n * Compute the audit matrix from pact files, ledger, and optional broker data.\n */\nexport function computeAuditMatrix(input: {\n contracted: PactInteractionKey[];\n ledger: LedgerRecord[];\n brokerVerifications?: BrokerVerification[];\n windowDays?: number;\n now?: Date;\n}): AuditMatrix {\n const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;\n const now = input.now ?? new Date();\n const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;\n\n const verificationFailures: ProviderVerificationRunEntry[] = [];\n const recentInteractions: InteractionLedgerEntry[] = [];\n\n for (const entry of input.ledger) {\n if (!inWindow(entry.observed_at, cutoff)) continue;\n if (isProviderVerificationRun(entry)) {\n verificationFailures.push(entry);\n continue;\n }\n if (isInteractionLedgerEntry(entry)) {\n recentInteractions.push(entry);\n }\n }\n\n const brokerByPair = new Map<string, BrokerVerification>();\n for (const b of input.brokerVerifications ?? []) {\n brokerByPair.set(pairKey(b.consumer, b.provider), b);\n }\n\n const testSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const prodSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const providerVerifiedByKey = new Map<string, InteractionLedgerEntry[]>();\n const anyObservedByKey = new Map<string, InteractionLedgerEntry[]>();\n\n for (const entry of recentInteractions) {\n const k = keyOf({\n consumer: entry.consumer,\n provider: entry.provider,\n interaction: entry.interaction,\n kind: entry.kind,\n interactionId: entry.interaction_id,\n });\n\n const push = (map: Map<string, InteractionLedgerEntry[]>) => {\n const arr = map.get(k) ?? [];\n arr.push(entry);\n map.set(k, arr);\n };\n\n push(anyObservedByKey);\n\n if (entry.source === 'test' && entry.role === 'consumer') {\n push(testSeenByKey);\n }\n if (entry.source === 'production') {\n push(prodSeenByKey);\n }\n if (entry.role === 'provider' && entry.outcome === 'passed') {\n push(providerVerifiedByKey);\n }\n }\n\n const contractedByKey = new Map<string, PactInteractionKey>();\n for (const c of input.contracted) {\n contractedByKey.set(keyOf(c), c);\n }\n\n const rows: AuditRow[] = [];\n\n function pushRow(parts: PactInteractionKey, k: string, isContracted: boolean): void {\n const testObs = testSeenByKey.get(k) ?? [];\n const prodObs = prodSeenByKey.get(k) ?? [];\n const providerObs = providerVerifiedByKey.get(k) ?? [];\n const allObs = anyObservedByKey.get(k) ?? [];\n const latest = allObs.toSorted((a, b) =>\n b.observed_at.localeCompare(a.observed_at),\n )[0];\n const broker = brokerByPair.get(pairKey(parts.consumer, parts.provider));\n\n rows.push({\n consumer: parts.consumer,\n provider: parts.provider,\n interaction: parts.interaction,\n interaction_id: parts.interactionId ?? latest?.interaction_id,\n kind: parts.kind,\n contracted: isContracted,\n observed: testObs.length > 0 || prodObs.length > 0,\n test_seen: testObs.length > 0,\n prod_seen: prodObs.length > 0,\n provider_verified: providerObs.length > 0,\n broker_verified: broker?.success === true,\n broker_verified_at: broker?.verifiedAt,\n broker_error: broker?.error,\n last_observed_at: latest?.observed_at,\n last_outcome: latest?.outcome,\n });\n }\n\n for (const [k, contracted] of contractedByKey) {\n pushRow(contracted, k, true);\n }\n for (const [k, observations] of anyObservedByKey) {\n if (contractedByKey.has(k)) continue;\n const first = observations[0]!;\n pushRow(\n {\n consumer: first.consumer,\n provider: first.provider,\n interaction: first.interaction,\n kind: first.kind,\n interactionId: first.interaction_id,\n },\n k,\n false,\n );\n }\n\n rows.sort((a, b) =>\n a.consumer.localeCompare(b.consumer) ||\n a.provider.localeCompare(b.provider) ||\n a.interaction.localeCompare(b.interaction),\n );\n\n const counts = {\n total: rows.length,\n contracted: rows.filter((r) => r.contracted).length,\n observed: rows.filter((r) => r.observed).length,\n contracted_and_test_seen: rows.filter((r) => r.contracted && r.test_seen).length,\n contracted_not_test_seen: rows.filter((r) => r.contracted && !r.test_seen).length,\n test_or_prod_seen_not_contracted: rows.filter(\n (r) => !r.contracted && (r.test_seen || r.prod_seen),\n ).length,\n test_seen: rows.filter((r) => r.test_seen).length,\n prod_seen: rows.filter((r) => r.prod_seen).length,\n provider_verified: rows.filter((r) => r.provider_verified).length,\n broker_verified: rows.filter((r) => r.broker_verified).length,\n };\n\n const matrix: AuditMatrix = {\n spec: AUDIT_MATRIX_SPEC,\n rows,\n counts,\n window_days: windowDays,\n generated_at: now.toISOString(),\n };\n\n if (verificationFailures.length > 0) {\n matrix.verification_failures = verificationFailures;\n }\n\n return matrix;\n}\n\nexport async function runAudit(opts: AuditOptions = {}): Promise<AuditMatrix> {\n const pactsDir = path.resolve(process.cwd(), opts.pactsDir ?? DEFAULT_PACTS_DIR);\n const contracted: PactInteractionKey[] = [];\n const pairs = new Set<string>();\n\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (!pact) continue;\n const interactions = interactionsFromPactFile(pact);\n contracted.push(...interactions);\n const consumer = pact.consumer?.name;\n const provider = pact.provider?.name;\n if (consumer && provider) {\n pairs.add(pairKey(consumer, provider));\n }\n }\n\n const ledger = readLedger(opts);\n\n let brokerVerifications: BrokerVerification[] | undefined;\n if (opts.broker) {\n brokerVerifications = await fetchBrokerVerifications(opts.broker, [...pairs].map((p) => {\n const [consumer, provider] = p.split('::');\n return { consumer: consumer!, provider: provider! };\n }));\n }\n\n return computeAuditMatrix({\n contracted,\n ledger,\n brokerVerifications,\n windowDays: opts.windowDays,\n });\n}\n\n/** Sync audit without broker (backward compatible for tests). */\nexport function runAuditSync(opts: Omit<AuditOptions, 'broker'> = {}): AuditMatrix {\n const pactsDir = path.resolve(process.cwd(), opts.pactsDir ?? DEFAULT_PACTS_DIR);\n const contracted: PactInteractionKey[] = [];\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (pact) contracted.push(...interactionsFromPactFile(pact));\n }\n const ledger = readLedger(opts);\n return computeAuditMatrix({\n contracted,\n ledger,\n windowDays: opts.windowDays,\n });\n}\n"],"mappings":";;;;;;;;AA2BA,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAE5B,SAAgB,MAAM,GAA+B;CACnD,MAAM,WAAW,EAAE,iBAAiB,EAAE;CACtC,OAAO,GAAG,EAAE,SAAS,IAAI,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI;AACrD;AAEA,SAAS,QAAQ,UAAkB,UAA0B;CAC3D,OAAO,GAAG,SAAS,IAAI;AACzB;AAEA,SAAS,SAAS,YAAoB,QAAyB;CAC7D,MAAM,IAAI,KAAK,MAAM,UAAU;CAC/B,OAAO,OAAO,SAAS,CAAC,KAAK,KAAK;AACpC;;;;AAKA,SAAgB,mBAAmB,OAMnB;CACd,MAAM,aAAa,MAAM,cAAc;CACvC,MAAM,MAAM,MAAM,uBAAO,IAAI,KAAK;CAClC,MAAM,SAAS,IAAI,QAAQ,IAAI,aAAa,KAAK,KAAK,KAAK;CAE3D,MAAM,uBAAuD,CAAC;CAC9D,MAAM,qBAA+C,CAAC;CAEtD,KAAK,MAAM,SAAS,MAAM,QAAQ;EAChC,IAAI,CAAC,SAAS,MAAM,aAAa,MAAM,GAAG;EAC1C,IAAIA,yCAA0B,KAAK,GAAG;GACpC,qBAAqB,KAAK,KAAK;GAC/B;EACF;EACA,IAAIC,wCAAyB,KAAK,GAChC,mBAAmB,KAAK,KAAK;CAEjC;CAEA,MAAM,+BAAe,IAAI,IAAgC;CACzD,KAAK,MAAM,KAAK,MAAM,uBAAuB,CAAC,GAC5C,aAAa,IAAI,QAAQ,EAAE,UAAU,EAAE,QAAQ,GAAG,CAAC;CAGrD,MAAM,gCAAgB,IAAI,IAAsC;CAChE,MAAM,gCAAgB,IAAI,IAAsC;CAChE,MAAM,wCAAwB,IAAI,IAAsC;CACxE,MAAM,mCAAmB,IAAI,IAAsC;CAEnE,KAAK,MAAM,SAAS,oBAAoB;EACtC,MAAM,IAAI,MAAM;GACd,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,MAAM,MAAM;GACZ,eAAe,MAAM;EACvB,CAAC;EAED,MAAM,QAAQ,QAA+C;GAC3D,MAAM,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC;GAC3B,IAAI,KAAK,KAAK;GACd,IAAI,IAAI,GAAG,GAAG;EAChB;EAEA,KAAK,gBAAgB;EAErB,IAAI,MAAM,WAAW,UAAU,MAAM,SAAS,YAC5C,KAAK,aAAa;EAEpB,IAAI,MAAM,WAAW,cACnB,KAAK,aAAa;EAEpB,IAAI,MAAM,SAAS,cAAc,MAAM,YAAY,UACjD,KAAK,qBAAqB;CAE9B;CAEA,MAAM,kCAAkB,IAAI,IAAgC;CAC5D,KAAK,MAAM,KAAK,MAAM,YACpB,gBAAgB,IAAI,MAAM,CAAC,GAAG,CAAC;CAGjC,MAAM,OAAmB,CAAC;CAE1B,SAAS,QAAQ,OAA2B,GAAW,cAA6B;EAClF,MAAM,UAAU,cAAc,IAAI,CAAC,KAAK,CAAC;EACzC,MAAM,UAAU,cAAc,IAAI,CAAC,KAAK,CAAC;EACzC,MAAM,cAAc,sBAAsB,IAAI,CAAC,KAAK,CAAC;EAErD,MAAM,UADS,iBAAiB,IAAI,CAAC,KAAK,CAAC,EACtB,CAAC,UAAU,GAAG,MACjC,EAAE,YAAY,cAAc,EAAE,WAAW,CAC3C,CAAC,CAAC;EACF,MAAM,SAAS,aAAa,IAAI,QAAQ,MAAM,UAAU,MAAM,QAAQ,CAAC;EAEvE,KAAK,KAAK;GACR,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,gBAAgB,MAAM,iBAAiB,QAAQ;GAC/C,MAAM,MAAM;GACZ,YAAY;GACZ,UAAU,QAAQ,SAAS,KAAK,QAAQ,SAAS;GACjD,WAAW,QAAQ,SAAS;GAC5B,WAAW,QAAQ,SAAS;GAC5B,mBAAmB,YAAY,SAAS;GACxC,iBAAiB,QAAQ,YAAY;GACrC,oBAAoB,QAAQ;GAC5B,cAAc,QAAQ;GACtB,kBAAkB,QAAQ;GAC1B,cAAc,QAAQ;EACxB,CAAC;CACH;CAEA,KAAK,MAAM,CAAC,GAAG,eAAe,iBAC5B,QAAQ,YAAY,GAAG,IAAI;CAE7B,KAAK,MAAM,CAAC,GAAG,iBAAiB,kBAAkB;EAChD,IAAI,gBAAgB,IAAI,CAAC,GAAG;EAC5B,MAAM,QAAQ,aAAa;EAC3B,QACE;GACE,UAAU,MAAM;GAChB,UAAU,MAAM;GAChB,aAAa,MAAM;GACnB,MAAM,MAAM;GACZ,eAAe,MAAM;EACvB,GACA,GACA,KACF;CACF;CAEA,KAAK,MAAM,GAAG,MACZ,EAAE,SAAS,cAAc,EAAE,QAAQ,KACnC,EAAE,SAAS,cAAc,EAAE,QAAQ,KACnC,EAAE,YAAY,cAAc,EAAE,WAAW,CAC3C;CAiBA,MAAM,SAAsB;EAC1B,MAAMC;EACN;EACA;GAjBA,OAAO,KAAK;GACZ,YAAY,KAAK,QAAQ,MAAM,EAAE,UAAU,CAAC,CAAC;GAC7C,UAAU,KAAK,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC;GACzC,0BAA0B,KAAK,QAAQ,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;GAC1E,0BAA0B,KAAK,QAAQ,MAAM,EAAE,cAAc,CAAC,EAAE,SAAS,CAAC,CAAC;GAC3E,kCAAkC,KAAK,QACpC,MAAM,CAAC,EAAE,eAAe,EAAE,aAAa,EAAE,UAC5C,CAAC,CAAC;GACF,WAAW,KAAK,QAAQ,MAAM,EAAE,SAAS,CAAC,CAAC;GAC3C,WAAW,KAAK,QAAQ,MAAM,EAAE,SAAS,CAAC,CAAC;GAC3C,mBAAmB,KAAK,QAAQ,MAAM,EAAE,iBAAiB,CAAC,CAAC;GAC3D,iBAAiB,KAAK,QAAQ,MAAM,EAAE,eAAe,CAAC,CAAC;EAMlD;EACL,aAAa;EACb,cAAc,IAAI,YAAY;CAChC;CAEA,IAAI,qBAAqB,SAAS,GAChC,OAAO,wBAAwB;CAGjC,OAAO;AACT;AAEA,eAAsB,SAAS,OAAqB,CAAC,GAAyB;CAC5E,MAAM,WAAWC,kBAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,YAAY,iBAAiB;CAC/E,MAAM,aAAmC,CAAC;CAC1C,MAAM,wBAAQ,IAAI,IAAY;CAE9B,KAAK,MAAM,QAAQC,gCAAc,QAAQ,GAAG;EAC1C,MAAM,OAAOC,gCAAc,IAAI;EAC/B,IAAI,CAAC,MAAM;EACX,MAAM,eAAeC,2CAAyB,IAAI;EAClD,WAAW,KAAK,GAAG,YAAY;EAC/B,MAAM,WAAW,KAAK,UAAU;EAChC,MAAM,WAAW,KAAK,UAAU;EAChC,IAAI,YAAY,UACd,MAAM,IAAI,QAAQ,UAAU,QAAQ,CAAC;CAEzC;CAEA,MAAM,SAASC,0BAAW,IAAI;CAE9B,IAAI;CACJ,IAAI,KAAK,QACP,sBAAsB,MAAMC,wCAAyB,KAAK,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,MAAM;EACtF,MAAM,CAAC,UAAU,YAAY,EAAE,MAAM,IAAI;EACzC,OAAO;GAAY;GAAqB;EAAU;CACpD,CAAC,CAAC;CAGJ,OAAO,mBAAmB;EACxB;EACA;EACA;EACA,YAAY,KAAK;CACnB,CAAC;AACH;;AAGA,SAAgB,aAAa,OAAqC,CAAC,GAAgB;CACjF,MAAM,WAAWL,kBAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,YAAY,iBAAiB;CAC/E,MAAM,aAAmC,CAAC;CAC1C,KAAK,MAAM,QAAQC,gCAAc,QAAQ,GAAG;EAC1C,MAAM,OAAOC,gCAAc,IAAI;EAC/B,IAAI,MAAM,WAAW,KAAK,GAAGC,2CAAyB,IAAI,CAAC;CAC7D;CAEA,OAAO,mBAAmB;EACxB;EACA,QAHaC,0BAAW,IAGnB;EACL,YAAY,KAAK;CACnB,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"audit.cjs","names":["isProviderVerificationRun","isInteractionLedgerEntry","AUDIT_MATRIX_SPEC","path","listPactFiles","parsePactFile","interactionsFromPactFile","readLedger","fetchBrokerVerifications"],"sources":["../src/audit.ts"],"sourcesContent":["import path from 'node:path';\nimport { fetchBrokerVerifications, type BrokerConfig } from './broker.js';\nimport { readLedger, type LedgerOptions } from './ledger.js';\nimport {\n interactionsFromPactFile,\n listPactFiles,\n parsePactFile,\n} from './pact-file.js';\nimport {\n AUDIT_MATRIX_SPEC,\n isInteractionLedgerEntry,\n isProviderVerificationRun,\n type AuditMatrix,\n type AuditRow,\n type BrokerVerification,\n type InteractionLedgerEntry,\n type LedgerRecord,\n type PactInteractionKey,\n type ProviderVerificationRunEntry,\n} from './types.js';\n\nexport interface AuditOptions extends LedgerOptions {\n pactsDir?: string;\n windowDays?: number;\n broker?: BrokerConfig;\n}\n\nconst DEFAULT_PACTS_DIR = './pacts';\nconst DEFAULT_WINDOW_DAYS = 14;\n\nexport function keyOf(k: PactInteractionKey): string {\n const identity = k.interactionId ?? k.interaction;\n return `${k.consumer}::${k.provider}::${k.kind}::${identity}`;\n}\n\nfunction pairKey(consumer: string, provider: string): string {\n return `${consumer}::${provider}`;\n}\n\nfunction inWindow(observedAt: string, cutoff: number): boolean {\n const t = Date.parse(observedAt);\n return Number.isFinite(t) && t >= cutoff;\n}\n\n/**\n * Compute the audit matrix from pact files, ledger, and optional broker data.\n */\nexport function computeAuditMatrix(input: {\n contracted: PactInteractionKey[];\n ledger: LedgerRecord[];\n brokerVerifications?: BrokerVerification[];\n windowDays?: number;\n now?: Date;\n}): AuditMatrix {\n const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;\n const now = input.now ?? new Date();\n const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;\n\n const verificationFailures: ProviderVerificationRunEntry[] = [];\n const recentInteractions: InteractionLedgerEntry[] = [];\n\n for (const entry of input.ledger) {\n if (!inWindow(entry.observed_at, cutoff)) continue;\n if (isProviderVerificationRun(entry)) {\n verificationFailures.push(entry);\n continue;\n }\n if (isInteractionLedgerEntry(entry)) {\n recentInteractions.push(entry);\n }\n }\n\n const brokerByPair = new Map<string, BrokerVerification>();\n for (const b of input.brokerVerifications ?? []) {\n brokerByPair.set(pairKey(b.consumer, b.provider), b);\n }\n\n const testSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const prodSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const providerVerifiedByKey = new Map<string, InteractionLedgerEntry[]>();\n const anyObservedByKey = new Map<string, InteractionLedgerEntry[]>();\n\n for (const entry of recentInteractions) {\n const k = keyOf({\n consumer: entry.consumer,\n provider: entry.provider,\n interaction: entry.interaction,\n kind: entry.kind,\n interactionId: entry.interaction_id,\n });\n\n const push = (map: Map<string, InteractionLedgerEntry[]>) => {\n const arr = map.get(k) ?? [];\n arr.push(entry);\n map.set(k, arr);\n };\n\n push(anyObservedByKey);\n\n if (entry.source === 'test' && entry.role === 'consumer') {\n push(testSeenByKey);\n }\n if (entry.source === 'production') {\n push(prodSeenByKey);\n }\n if (entry.role === 'provider' && entry.outcome === 'passed') {\n push(providerVerifiedByKey);\n }\n }\n\n const contractedByKey = new Map<string, PactInteractionKey>();\n for (const c of input.contracted) {\n contractedByKey.set(keyOf(c), c);\n }\n\n const rows: AuditRow[] = [];\n\n function pushRow(\n parts: PactInteractionKey,\n k: string,\n isContracted: boolean,\n ): void {\n const testObs = testSeenByKey.get(k) ?? [];\n const prodObs = prodSeenByKey.get(k) ?? [];\n const providerObs = providerVerifiedByKey.get(k) ?? [];\n const allObs = anyObservedByKey.get(k) ?? [];\n const latest = allObs.toSorted((a, b) =>\n b.observed_at.localeCompare(a.observed_at),\n )[0];\n const broker = brokerByPair.get(pairKey(parts.consumer, parts.provider));\n\n rows.push({\n consumer: parts.consumer,\n provider: parts.provider,\n interaction: parts.interaction,\n interaction_id: parts.interactionId ?? latest?.interaction_id,\n kind: parts.kind,\n contracted: isContracted,\n observed: testObs.length > 0 || prodObs.length > 0,\n test_seen: testObs.length > 0,\n prod_seen: prodObs.length > 0,\n provider_verified: providerObs.length > 0,\n broker_verified: broker?.success === true,\n broker_verified_at: broker?.verifiedAt,\n broker_error: broker?.error,\n last_observed_at: latest?.observed_at,\n last_outcome: latest?.outcome,\n });\n }\n\n for (const [k, contracted] of contractedByKey) {\n pushRow(contracted, k, true);\n }\n for (const [k, observations] of anyObservedByKey) {\n if (contractedByKey.has(k)) continue;\n const first = observations[0]!;\n pushRow(\n {\n consumer: first.consumer,\n provider: first.provider,\n interaction: first.interaction,\n kind: first.kind,\n interactionId: first.interaction_id,\n },\n k,\n false,\n );\n }\n\n rows.sort(\n (a, b) =>\n a.consumer.localeCompare(b.consumer) ||\n a.provider.localeCompare(b.provider) ||\n a.interaction.localeCompare(b.interaction),\n );\n\n const counts = {\n total: rows.length,\n contracted: rows.filter((r) => r.contracted).length,\n observed: rows.filter((r) => r.observed).length,\n contracted_and_test_seen: rows.filter((r) => r.contracted && r.test_seen)\n .length,\n contracted_not_test_seen: rows.filter((r) => r.contracted && !r.test_seen)\n .length,\n test_or_prod_seen_not_contracted: rows.filter(\n (r) => !r.contracted && (r.test_seen || r.prod_seen),\n ).length,\n test_seen: rows.filter((r) => r.test_seen).length,\n prod_seen: rows.filter((r) => r.prod_seen).length,\n provider_verified: rows.filter((r) => r.provider_verified).length,\n broker_verified: rows.filter((r) => r.broker_verified).length,\n };\n\n const matrix: AuditMatrix = {\n spec: AUDIT_MATRIX_SPEC,\n rows,\n counts,\n window_days: windowDays,\n generated_at: now.toISOString(),\n };\n\n if (verificationFailures.length > 0) {\n matrix.verification_failures = verificationFailures;\n }\n\n return matrix;\n}\n\nexport async function runAudit(opts: AuditOptions = {}): Promise<AuditMatrix> {\n const pactsDir = path.resolve(\n process.cwd(),\n opts.pactsDir ?? DEFAULT_PACTS_DIR,\n );\n const contracted: PactInteractionKey[] = [];\n const pairs = new Set<string>();\n\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (!pact) continue;\n const interactions = interactionsFromPactFile(pact);\n contracted.push(...interactions);\n const consumer = pact.consumer?.name;\n const provider = pact.provider?.name;\n if (consumer && provider) {\n pairs.add(pairKey(consumer, provider));\n }\n }\n\n const ledger = readLedger(opts);\n\n let brokerVerifications: BrokerVerification[] | undefined;\n if (opts.broker) {\n brokerVerifications = await fetchBrokerVerifications(\n opts.broker,\n [...pairs].map((p) => {\n const [consumer, provider] = p.split('::');\n return { consumer: consumer!, provider: provider! };\n }),\n );\n }\n\n return computeAuditMatrix({\n contracted,\n ledger,\n brokerVerifications,\n windowDays: opts.windowDays,\n });\n}\n\n/** Sync audit without broker (backward compatible for tests). */\nexport function runAuditSync(\n opts: Omit<AuditOptions, 'broker'> = {},\n): AuditMatrix {\n const pactsDir = path.resolve(\n process.cwd(),\n opts.pactsDir ?? DEFAULT_PACTS_DIR,\n );\n const contracted: PactInteractionKey[] = [];\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (pact) contracted.push(...interactionsFromPactFile(pact));\n }\n const ledger = readLedger(opts);\n return computeAuditMatrix({\n contracted,\n ledger,\n windowDays: opts.windowDays,\n });\n}\n"],"mappings":"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"}
|
package/dist/audit.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { d as PactInteractionKey, i as BrokerVerification, n as AuditMatrix, s as LedgerRecord } from "./types-
|
|
1
|
+
import { d as PactInteractionKey, i as BrokerVerification, n as AuditMatrix, s as LedgerRecord } from "./types-DpHqSzxb.cjs";
|
|
2
2
|
import { BrokerConfig } from "./broker.cjs";
|
|
3
|
-
import { t as LedgerOptions } from "./ledger-
|
|
4
|
-
|
|
3
|
+
import { t as LedgerOptions } from "./ledger-yzVOA9iE.cjs";
|
|
5
4
|
//#region src/audit.d.ts
|
|
6
5
|
interface AuditOptions extends LedgerOptions {
|
|
7
6
|
pactsDir?: string;
|
package/dist/audit.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.cts","names":[],"sources":["../src/audit.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.cts","names":[],"sources":["../src/audit.ts"],"mappings":";;;;UAqBiB,qBAAqB;EACpC;EACA;EACA,SAAS;;iBAMK,MAAM,GAAG;;;;iBAiBT,mBAAmB;EACjC,YAAY;EACZ,QAAQ;EACR,sBAAsB;EACtB;EACA,MAAM;IACJ;iBA2JkB,SAAS,OAAM,eAAoB,QAAQ;;iBA0CjD,aACd,OAAM,KAAK,0BACV"}
|
package/dist/audit.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { d as PactInteractionKey, i as BrokerVerification, n as AuditMatrix, s as LedgerRecord } from "./types-
|
|
1
|
+
import { d as PactInteractionKey, i as BrokerVerification, n as AuditMatrix, s as LedgerRecord } from "./types-DpHqSzxb.js";
|
|
2
2
|
import { BrokerConfig } from "./broker.js";
|
|
3
|
-
import { t as LedgerOptions } from "./ledger-
|
|
4
|
-
|
|
3
|
+
import { t as LedgerOptions } from "./ledger-CBUbpQr-.js";
|
|
5
4
|
//#region src/audit.d.ts
|
|
6
5
|
interface AuditOptions extends LedgerOptions {
|
|
7
6
|
pactsDir?: string;
|
package/dist/audit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","names":[],"sources":["../src/audit.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","names":[],"sources":["../src/audit.ts"],"mappings":";;;;UAqBiB,qBAAqB;EACpC;EACA;EACA,SAAS;;iBAMK,MAAM,GAAG;;;;iBAiBT,mBAAmB;EACjC,YAAY;EACZ,QAAQ;EACR,sBAAsB;EACtB;EACA,MAAM;IACJ;iBA2JkB,SAAS,OAAM,eAAoB,QAAQ;;iBA0CjD,aACd,OAAM,KAAK,0BACV"}
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","names":[],"sources":["../src/audit.ts"],"sourcesContent":["import path from 'node:path';\nimport { fetchBrokerVerifications, type BrokerConfig } from './broker.js';\nimport { readLedger, type LedgerOptions } from './ledger.js';\nimport {\n interactionsFromPactFile,\n listPactFiles,\n parsePactFile,\n} from './pact-file.js';\nimport {\n AUDIT_MATRIX_SPEC,\n isInteractionLedgerEntry,\n isProviderVerificationRun,\n type AuditMatrix,\n type AuditRow,\n type BrokerVerification,\n type InteractionLedgerEntry,\n type LedgerRecord,\n type PactInteractionKey,\n type ProviderVerificationRunEntry,\n} from './types.js';\n\nexport interface AuditOptions extends LedgerOptions {\n pactsDir?: string;\n windowDays?: number;\n broker?: BrokerConfig;\n}\n\nconst DEFAULT_PACTS_DIR = './pacts';\nconst DEFAULT_WINDOW_DAYS = 14;\n\nexport function keyOf(k: PactInteractionKey): string {\n const identity = k.interactionId ?? k.interaction;\n return `${k.consumer}::${k.provider}::${k.kind}::${identity}`;\n}\n\nfunction pairKey(consumer: string, provider: string): string {\n return `${consumer}::${provider}`;\n}\n\nfunction inWindow(observedAt: string, cutoff: number): boolean {\n const t = Date.parse(observedAt);\n return Number.isFinite(t) && t >= cutoff;\n}\n\n/**\n * Compute the audit matrix from pact files, ledger, and optional broker data.\n */\nexport function computeAuditMatrix(input: {\n contracted: PactInteractionKey[];\n ledger: LedgerRecord[];\n brokerVerifications?: BrokerVerification[];\n windowDays?: number;\n now?: Date;\n}): AuditMatrix {\n const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;\n const now = input.now ?? new Date();\n const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;\n\n const verificationFailures: ProviderVerificationRunEntry[] = [];\n const recentInteractions: InteractionLedgerEntry[] = [];\n\n for (const entry of input.ledger) {\n if (!inWindow(entry.observed_at, cutoff)) continue;\n if (isProviderVerificationRun(entry)) {\n verificationFailures.push(entry);\n continue;\n }\n if (isInteractionLedgerEntry(entry)) {\n recentInteractions.push(entry);\n }\n }\n\n const brokerByPair = new Map<string, BrokerVerification>();\n for (const b of input.brokerVerifications ?? []) {\n brokerByPair.set(pairKey(b.consumer, b.provider), b);\n }\n\n const testSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const prodSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const providerVerifiedByKey = new Map<string, InteractionLedgerEntry[]>();\n const anyObservedByKey = new Map<string, InteractionLedgerEntry[]>();\n\n for (const entry of recentInteractions) {\n const k = keyOf({\n consumer: entry.consumer,\n provider: entry.provider,\n interaction: entry.interaction,\n kind: entry.kind,\n interactionId: entry.interaction_id,\n });\n\n const push = (map: Map<string, InteractionLedgerEntry[]>) => {\n const arr = map.get(k) ?? [];\n arr.push(entry);\n map.set(k, arr);\n };\n\n push(anyObservedByKey);\n\n if (entry.source === 'test' && entry.role === 'consumer') {\n push(testSeenByKey);\n }\n if (entry.source === 'production') {\n push(prodSeenByKey);\n }\n if (entry.role === 'provider' && entry.outcome === 'passed') {\n push(providerVerifiedByKey);\n }\n }\n\n const contractedByKey = new Map<string, PactInteractionKey>();\n for (const c of input.contracted) {\n contractedByKey.set(keyOf(c), c);\n }\n\n const rows: AuditRow[] = [];\n\n function pushRow(parts: PactInteractionKey, k: string, isContracted: boolean): void {\n const testObs = testSeenByKey.get(k) ?? [];\n const prodObs = prodSeenByKey.get(k) ?? [];\n const providerObs = providerVerifiedByKey.get(k) ?? [];\n const allObs = anyObservedByKey.get(k) ?? [];\n const latest = allObs.toSorted((a, b) =>\n b.observed_at.localeCompare(a.observed_at),\n )[0];\n const broker = brokerByPair.get(pairKey(parts.consumer, parts.provider));\n\n rows.push({\n consumer: parts.consumer,\n provider: parts.provider,\n interaction: parts.interaction,\n interaction_id: parts.interactionId ?? latest?.interaction_id,\n kind: parts.kind,\n contracted: isContracted,\n observed: testObs.length > 0 || prodObs.length > 0,\n test_seen: testObs.length > 0,\n prod_seen: prodObs.length > 0,\n provider_verified: providerObs.length > 0,\n broker_verified: broker?.success === true,\n broker_verified_at: broker?.verifiedAt,\n broker_error: broker?.error,\n last_observed_at: latest?.observed_at,\n last_outcome: latest?.outcome,\n });\n }\n\n for (const [k, contracted] of contractedByKey) {\n pushRow(contracted, k, true);\n }\n for (const [k, observations] of anyObservedByKey) {\n if (contractedByKey.has(k)) continue;\n const first = observations[0]!;\n pushRow(\n {\n consumer: first.consumer,\n provider: first.provider,\n interaction: first.interaction,\n kind: first.kind,\n interactionId: first.interaction_id,\n },\n k,\n false,\n );\n }\n\n rows.sort((a, b) =>\n a.consumer.localeCompare(b.consumer) ||\n a.provider.localeCompare(b.provider) ||\n a.interaction.localeCompare(b.interaction),\n );\n\n const counts = {\n total: rows.length,\n contracted: rows.filter((r) => r.contracted).length,\n observed: rows.filter((r) => r.observed).length,\n contracted_and_test_seen: rows.filter((r) => r.contracted && r.test_seen).length,\n contracted_not_test_seen: rows.filter((r) => r.contracted && !r.test_seen).length,\n test_or_prod_seen_not_contracted: rows.filter(\n (r) => !r.contracted && (r.test_seen || r.prod_seen),\n ).length,\n test_seen: rows.filter((r) => r.test_seen).length,\n prod_seen: rows.filter((r) => r.prod_seen).length,\n provider_verified: rows.filter((r) => r.provider_verified).length,\n broker_verified: rows.filter((r) => r.broker_verified).length,\n };\n\n const matrix: AuditMatrix = {\n spec: AUDIT_MATRIX_SPEC,\n rows,\n counts,\n window_days: windowDays,\n generated_at: now.toISOString(),\n };\n\n if (verificationFailures.length > 0) {\n matrix.verification_failures = verificationFailures;\n }\n\n return matrix;\n}\n\nexport async function runAudit(opts: AuditOptions = {}): Promise<AuditMatrix> {\n const pactsDir = path.resolve(process.cwd(), opts.pactsDir ?? DEFAULT_PACTS_DIR);\n const contracted: PactInteractionKey[] = [];\n const pairs = new Set<string>();\n\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (!pact) continue;\n const interactions = interactionsFromPactFile(pact);\n contracted.push(...interactions);\n const consumer = pact.consumer?.name;\n const provider = pact.provider?.name;\n if (consumer && provider) {\n pairs.add(pairKey(consumer, provider));\n }\n }\n\n const ledger = readLedger(opts);\n\n let brokerVerifications: BrokerVerification[] | undefined;\n if (opts.broker) {\n brokerVerifications = await fetchBrokerVerifications(opts.broker, [...pairs].map((p) => {\n const [consumer, provider] = p.split('::');\n return { consumer: consumer!, provider: provider! };\n }));\n }\n\n return computeAuditMatrix({\n contracted,\n ledger,\n brokerVerifications,\n windowDays: opts.windowDays,\n });\n}\n\n/** Sync audit without broker (backward compatible for tests). */\nexport function runAuditSync(opts: Omit<AuditOptions, 'broker'> = {}): AuditMatrix {\n const pactsDir = path.resolve(process.cwd(), opts.pactsDir ?? DEFAULT_PACTS_DIR);\n const contracted: PactInteractionKey[] = [];\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (pact) contracted.push(...interactionsFromPactFile(pact));\n }\n const ledger = readLedger(opts);\n return computeAuditMatrix({\n contracted,\n ledger,\n windowDays: opts.windowDays,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"audit.js","names":[],"sources":["../src/audit.ts"],"sourcesContent":["import path from 'node:path';\nimport { fetchBrokerVerifications, type BrokerConfig } from './broker.js';\nimport { readLedger, type LedgerOptions } from './ledger.js';\nimport {\n interactionsFromPactFile,\n listPactFiles,\n parsePactFile,\n} from './pact-file.js';\nimport {\n AUDIT_MATRIX_SPEC,\n isInteractionLedgerEntry,\n isProviderVerificationRun,\n type AuditMatrix,\n type AuditRow,\n type BrokerVerification,\n type InteractionLedgerEntry,\n type LedgerRecord,\n type PactInteractionKey,\n type ProviderVerificationRunEntry,\n} from './types.js';\n\nexport interface AuditOptions extends LedgerOptions {\n pactsDir?: string;\n windowDays?: number;\n broker?: BrokerConfig;\n}\n\nconst DEFAULT_PACTS_DIR = './pacts';\nconst DEFAULT_WINDOW_DAYS = 14;\n\nexport function keyOf(k: PactInteractionKey): string {\n const identity = k.interactionId ?? k.interaction;\n return `${k.consumer}::${k.provider}::${k.kind}::${identity}`;\n}\n\nfunction pairKey(consumer: string, provider: string): string {\n return `${consumer}::${provider}`;\n}\n\nfunction inWindow(observedAt: string, cutoff: number): boolean {\n const t = Date.parse(observedAt);\n return Number.isFinite(t) && t >= cutoff;\n}\n\n/**\n * Compute the audit matrix from pact files, ledger, and optional broker data.\n */\nexport function computeAuditMatrix(input: {\n contracted: PactInteractionKey[];\n ledger: LedgerRecord[];\n brokerVerifications?: BrokerVerification[];\n windowDays?: number;\n now?: Date;\n}): AuditMatrix {\n const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;\n const now = input.now ?? new Date();\n const cutoff = now.getTime() - windowDays * 24 * 60 * 60 * 1000;\n\n const verificationFailures: ProviderVerificationRunEntry[] = [];\n const recentInteractions: InteractionLedgerEntry[] = [];\n\n for (const entry of input.ledger) {\n if (!inWindow(entry.observed_at, cutoff)) continue;\n if (isProviderVerificationRun(entry)) {\n verificationFailures.push(entry);\n continue;\n }\n if (isInteractionLedgerEntry(entry)) {\n recentInteractions.push(entry);\n }\n }\n\n const brokerByPair = new Map<string, BrokerVerification>();\n for (const b of input.brokerVerifications ?? []) {\n brokerByPair.set(pairKey(b.consumer, b.provider), b);\n }\n\n const testSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const prodSeenByKey = new Map<string, InteractionLedgerEntry[]>();\n const providerVerifiedByKey = new Map<string, InteractionLedgerEntry[]>();\n const anyObservedByKey = new Map<string, InteractionLedgerEntry[]>();\n\n for (const entry of recentInteractions) {\n const k = keyOf({\n consumer: entry.consumer,\n provider: entry.provider,\n interaction: entry.interaction,\n kind: entry.kind,\n interactionId: entry.interaction_id,\n });\n\n const push = (map: Map<string, InteractionLedgerEntry[]>) => {\n const arr = map.get(k) ?? [];\n arr.push(entry);\n map.set(k, arr);\n };\n\n push(anyObservedByKey);\n\n if (entry.source === 'test' && entry.role === 'consumer') {\n push(testSeenByKey);\n }\n if (entry.source === 'production') {\n push(prodSeenByKey);\n }\n if (entry.role === 'provider' && entry.outcome === 'passed') {\n push(providerVerifiedByKey);\n }\n }\n\n const contractedByKey = new Map<string, PactInteractionKey>();\n for (const c of input.contracted) {\n contractedByKey.set(keyOf(c), c);\n }\n\n const rows: AuditRow[] = [];\n\n function pushRow(\n parts: PactInteractionKey,\n k: string,\n isContracted: boolean,\n ): void {\n const testObs = testSeenByKey.get(k) ?? [];\n const prodObs = prodSeenByKey.get(k) ?? [];\n const providerObs = providerVerifiedByKey.get(k) ?? [];\n const allObs = anyObservedByKey.get(k) ?? [];\n const latest = allObs.toSorted((a, b) =>\n b.observed_at.localeCompare(a.observed_at),\n )[0];\n const broker = brokerByPair.get(pairKey(parts.consumer, parts.provider));\n\n rows.push({\n consumer: parts.consumer,\n provider: parts.provider,\n interaction: parts.interaction,\n interaction_id: parts.interactionId ?? latest?.interaction_id,\n kind: parts.kind,\n contracted: isContracted,\n observed: testObs.length > 0 || prodObs.length > 0,\n test_seen: testObs.length > 0,\n prod_seen: prodObs.length > 0,\n provider_verified: providerObs.length > 0,\n broker_verified: broker?.success === true,\n broker_verified_at: broker?.verifiedAt,\n broker_error: broker?.error,\n last_observed_at: latest?.observed_at,\n last_outcome: latest?.outcome,\n });\n }\n\n for (const [k, contracted] of contractedByKey) {\n pushRow(contracted, k, true);\n }\n for (const [k, observations] of anyObservedByKey) {\n if (contractedByKey.has(k)) continue;\n const first = observations[0]!;\n pushRow(\n {\n consumer: first.consumer,\n provider: first.provider,\n interaction: first.interaction,\n kind: first.kind,\n interactionId: first.interaction_id,\n },\n k,\n false,\n );\n }\n\n rows.sort(\n (a, b) =>\n a.consumer.localeCompare(b.consumer) ||\n a.provider.localeCompare(b.provider) ||\n a.interaction.localeCompare(b.interaction),\n );\n\n const counts = {\n total: rows.length,\n contracted: rows.filter((r) => r.contracted).length,\n observed: rows.filter((r) => r.observed).length,\n contracted_and_test_seen: rows.filter((r) => r.contracted && r.test_seen)\n .length,\n contracted_not_test_seen: rows.filter((r) => r.contracted && !r.test_seen)\n .length,\n test_or_prod_seen_not_contracted: rows.filter(\n (r) => !r.contracted && (r.test_seen || r.prod_seen),\n ).length,\n test_seen: rows.filter((r) => r.test_seen).length,\n prod_seen: rows.filter((r) => r.prod_seen).length,\n provider_verified: rows.filter((r) => r.provider_verified).length,\n broker_verified: rows.filter((r) => r.broker_verified).length,\n };\n\n const matrix: AuditMatrix = {\n spec: AUDIT_MATRIX_SPEC,\n rows,\n counts,\n window_days: windowDays,\n generated_at: now.toISOString(),\n };\n\n if (verificationFailures.length > 0) {\n matrix.verification_failures = verificationFailures;\n }\n\n return matrix;\n}\n\nexport async function runAudit(opts: AuditOptions = {}): Promise<AuditMatrix> {\n const pactsDir = path.resolve(\n process.cwd(),\n opts.pactsDir ?? DEFAULT_PACTS_DIR,\n );\n const contracted: PactInteractionKey[] = [];\n const pairs = new Set<string>();\n\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (!pact) continue;\n const interactions = interactionsFromPactFile(pact);\n contracted.push(...interactions);\n const consumer = pact.consumer?.name;\n const provider = pact.provider?.name;\n if (consumer && provider) {\n pairs.add(pairKey(consumer, provider));\n }\n }\n\n const ledger = readLedger(opts);\n\n let brokerVerifications: BrokerVerification[] | undefined;\n if (opts.broker) {\n brokerVerifications = await fetchBrokerVerifications(\n opts.broker,\n [...pairs].map((p) => {\n const [consumer, provider] = p.split('::');\n return { consumer: consumer!, provider: provider! };\n }),\n );\n }\n\n return computeAuditMatrix({\n contracted,\n ledger,\n brokerVerifications,\n windowDays: opts.windowDays,\n });\n}\n\n/** Sync audit without broker (backward compatible for tests). */\nexport function runAuditSync(\n opts: Omit<AuditOptions, 'broker'> = {},\n): AuditMatrix {\n const pactsDir = path.resolve(\n process.cwd(),\n opts.pactsDir ?? DEFAULT_PACTS_DIR,\n );\n const contracted: PactInteractionKey[] = [];\n for (const file of listPactFiles(pactsDir)) {\n const pact = parsePactFile(file);\n if (pact) contracted.push(...interactionsFromPactFile(pact));\n }\n const ledger = readLedger(opts);\n return computeAuditMatrix({\n contracted,\n ledger,\n windowDays: opts.windowDays,\n });\n}\n"],"mappings":"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"}
|
package/dist/auto-wrap.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-wrap.cjs","names":["buildPactAttributes","outcomeAttribute","LEDGER_ENTRY_SPEC"],"sources":["../src/auto-wrap.ts"],"sourcesContent":["/**\n * Auto-wrap entry for vitest / jest setup files.\n *\n * Importing this module monkey-patches `@pact-foundation/pact`'s\n * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`\n * so every contract test in the project records a ledger entry without\n * users having to wrap each call by hand.\n *\n * Usage (vitest):\n *\n * ```ts\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * export default defineConfig({\n * test: { setupFiles: ['autotel-pact/auto-wrap'] },\n * });\n * ```\n *\n * Usage (jest):\n *\n * ```js\n * // jest.config.js\n * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };\n * ```\n *\n * Configuration is via environment variables — there's no API surface,\n * because setup files don't get parameters:\n *\n * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id\n * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir\n *\n * Idempotent: importing twice is a no-op.\n *\n * Safe when Pact-JS isn't installed: logs a single warning and exits\n * cleanly. The same setup file can stay in place for a repo whose\n * non-contract test packages don't have Pact as a dependency.\n */\n\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { createRequire } from 'node:module';\nimport { buildPactAttributes, outcomeAttribute } from './attrs.js';\nimport { appendLedgerEntry } from './ledger.js';\nimport { LEDGER_ENTRY_SPEC, type InteractionLedgerEntry, type PactInteractionMeta } from './types.js';\nimport type { HttpInteraction } from './wrapper-http.js';\nimport type { ReifiedMessage } from './wrapper.js';\n\nconst INSTALLED = Symbol.for('autotel-pact:auto-wrap-installed');\n\ninterface PactJsModule {\n MessageConsumerPact?: { prototype: Record<string | symbol, unknown> };\n PactV3?: { prototype: Record<string | symbol, unknown> };\n}\n\n/**\n * Install the auto-wrap. Called at module load when this file is imported.\n * Exposed for tests + explicit-invocation users.\n *\n * @param pactModule Override Pact-JS resolution. When omitted, requires\n * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.\n *\n * Returns `true` if Pact-JS was found and patched, `false` otherwise.\n */\nexport function installAutoWrap(pactModule?: PactJsModule): boolean {\n const pactJs = pactModule ?? loadPactJs();\n if (!pactJs) return false;\n\n patchMessagePact(pactJs);\n patchHttpPact(pactJs);\n return true;\n}\n\nfunction loadPactJs(): PactJsModule | undefined {\n try {\n // Use createRequire so we work from both ESM and CJS without\n // pulling Pact-JS into the dynamic import graph.\n const require = createRequire(import.meta.url);\n return require('@pact-foundation/pact') as PactJsModule;\n } catch {\n process.stderr.write(\n 'autotel-pact/auto-wrap: @pact-foundation/pact not installed — skipping.\\n',\n );\n return undefined;\n }\n}\n\nfunction patchMessagePact(mod: PactJsModule): void {\n const ctor = mod.MessageConsumerPact;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n const originalVerify = proto.verify as (\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ) => Promise<unknown>;\n\n proto.verify = async function patchedVerify(\n this: { config: { consumer: string; provider: string } },\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ): Promise<unknown> {\n const start = process.hrtime.bigint();\n let captured: ReifiedMessage | undefined;\n const consumer = this.config?.consumer ?? '<unknown>';\n const provider = this.config?.provider ?? '<unknown>';\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'message',\n });\n\n try {\n const result = await originalVerify.call(this, async (reified) => {\n captured = reified;\n const meta: PactInteractionMeta = {\n consumer,\n provider,\n description: reified.description ?? '<unknown>',\n states: (reified.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n };\n span.setAttributes(buildPactAttributes(meta));\n return handler(reified);\n });\n span.setAttributes(outcomeAttribute('passed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'passed',\n start,\n });\n return result;\n } catch (error) {\n span.setAttributes(outcomeAttribute('failed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'failed',\n start,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction patchHttpPact(mod: PactJsModule): void {\n const ctor = mod.PactV3;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n // Track interactions added to each PactV3 instance. Cleared after\n // executeTest emits ledger entries for them, so the same instance can\n // be reused across multiple test cases.\n const tracked = new WeakMap<object, HttpInteraction[]>();\n\n const originalAdd = proto.addInteraction as (i: HttpInteraction) => unknown;\n proto.addInteraction = function patchedAddInteraction(this: object, interaction: HttpInteraction) {\n const list = tracked.get(this) ?? [];\n list.push(interaction);\n tracked.set(this, list);\n return originalAdd.call(this, interaction);\n };\n\n const originalExecute = proto.executeTest as <T>(\n fn: (server: { url: string; port: number }) => Promise<T>,\n ) => Promise<T | undefined>;\n\n proto.executeTest = async function patchedExecuteTest<T>(\n this: { opts?: { consumer?: string; provider?: string } },\n fn: (server: { url: string; port: number }) => Promise<T>,\n ): Promise<T | undefined> {\n const start = process.hrtime.bigint();\n const consumer = this.opts?.consumer ?? '<unknown>';\n const provider = this.opts?.provider ?? '<unknown>';\n const interactions = tracked.get(this) ?? [];\n tracked.set(this, []);\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'http',\n });\n // If multiple interactions were added before executeTest, the span\n // attributes describe the first; ledger entries are still written\n // for each. Most real tests use one interaction per executeTest.\n const first = interactions[0];\n if (first) {\n span.setAttributes(\n buildPactAttributes({\n consumer,\n provider,\n description: first.uponReceiving,\n states: (first.states ?? []).map((s) => s.description),\n kind: 'http',\n }),\n );\n }\n\n try {\n const result = (await originalExecute.call(this, fn)) as T | undefined;\n span.setAttributes(outcomeAttribute('passed'));\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'passed',\n start,\n });\n }\n return result;\n } catch (error_) {\n span.setAttributes(outcomeAttribute('failed'));\n const error = error_ instanceof Error ? error_.message : String(error_);\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'failed',\n start,\n error,\n });\n }\n throw error_;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction writeAutoLedgerEntry(args: {\n consumer: string;\n provider: string;\n interaction: string;\n states: string[];\n kind: 'message' | 'http';\n outcome: 'passed' | 'failed';\n start: bigint;\n error?: string;\n}): void {\n const ctx = getActiveSpan()?.spanContext();\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: args.consumer,\n provider: args.provider,\n interaction: args.interaction,\n states: args.states,\n kind: args.kind,\n source: 'test',\n role: 'consumer',\n outcome: args.outcome,\n duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,\n observed_at: new Date().toISOString(),\n trace_id: ctx?.traceId,\n span_id: ctx?.spanId,\n run_id: process.env.AUTOTEL_PACT_RUN_ID,\n git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,\n error: args.error,\n };\n appendLedgerEntry(entry);\n}\n\n// Install on import — this file is meant to be loaded once as a setup file.\ninstallAutoWrap();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auto-wrap.cjs","names":["buildPactAttributes","outcomeAttribute","LEDGER_ENTRY_SPEC"],"sources":["../src/auto-wrap.ts"],"sourcesContent":["/**\n * Auto-wrap entry for vitest / jest setup files.\n *\n * Importing this module monkey-patches `@pact-foundation/pact`'s\n * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`\n * so every contract test in the project records a ledger entry without\n * users having to wrap each call by hand.\n *\n * Usage (vitest):\n *\n * ```ts\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * export default defineConfig({\n * test: { setupFiles: ['autotel-pact/auto-wrap'] },\n * });\n * ```\n *\n * Usage (jest):\n *\n * ```js\n * // jest.config.js\n * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };\n * ```\n *\n * Configuration is via environment variables — there's no API surface,\n * because setup files don't get parameters:\n *\n * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id\n * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir\n *\n * Idempotent: importing twice is a no-op.\n *\n * Safe when Pact-JS isn't installed: logs a single warning and exits\n * cleanly. The same setup file can stay in place for a repo whose\n * non-contract test packages don't have Pact as a dependency.\n */\n\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { createRequire } from 'node:module';\nimport { buildPactAttributes, outcomeAttribute } from './attrs.js';\nimport { appendLedgerEntry } from './ledger.js';\nimport {\n LEDGER_ENTRY_SPEC,\n type InteractionLedgerEntry,\n type PactInteractionMeta,\n} from './types.js';\nimport type { HttpInteraction } from './wrapper-http.js';\nimport type { ReifiedMessage } from './wrapper.js';\n\nconst INSTALLED = Symbol.for('autotel-pact:auto-wrap-installed');\n\ninterface PactJsModule {\n MessageConsumerPact?: { prototype: Record<string | symbol, unknown> };\n PactV3?: { prototype: Record<string | symbol, unknown> };\n}\n\n/**\n * Install the auto-wrap. Called at module load when this file is imported.\n * Exposed for tests + explicit-invocation users.\n *\n * @param pactModule Override Pact-JS resolution. When omitted, requires\n * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.\n *\n * Returns `true` if Pact-JS was found and patched, `false` otherwise.\n */\nexport function installAutoWrap(pactModule?: PactJsModule): boolean {\n const pactJs = pactModule ?? loadPactJs();\n if (!pactJs) return false;\n\n patchMessagePact(pactJs);\n patchHttpPact(pactJs);\n return true;\n}\n\nfunction loadPactJs(): PactJsModule | undefined {\n try {\n // Use createRequire so we work from both ESM and CJS without\n // pulling Pact-JS into the dynamic import graph.\n const require = createRequire(import.meta.url);\n return require('@pact-foundation/pact') as PactJsModule;\n } catch {\n process.stderr.write(\n 'autotel-pact/auto-wrap: @pact-foundation/pact not installed — skipping.\\n',\n );\n return undefined;\n }\n}\n\nfunction patchMessagePact(mod: PactJsModule): void {\n const ctor = mod.MessageConsumerPact;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n const originalVerify = proto.verify as (\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ) => Promise<unknown>;\n\n proto.verify = async function patchedVerify(\n this: { config: { consumer: string; provider: string } },\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ): Promise<unknown> {\n const start = process.hrtime.bigint();\n let captured: ReifiedMessage | undefined;\n const consumer = this.config?.consumer ?? '<unknown>';\n const provider = this.config?.provider ?? '<unknown>';\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'message',\n });\n\n try {\n const result = await originalVerify.call(this, async (reified) => {\n captured = reified;\n const meta: PactInteractionMeta = {\n consumer,\n provider,\n description: reified.description ?? '<unknown>',\n states: (reified.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n };\n span.setAttributes(buildPactAttributes(meta));\n return handler(reified);\n });\n span.setAttributes(outcomeAttribute('passed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'passed',\n start,\n });\n return result;\n } catch (error) {\n span.setAttributes(outcomeAttribute('failed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'failed',\n start,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction patchHttpPact(mod: PactJsModule): void {\n const ctor = mod.PactV3;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n // Track interactions added to each PactV3 instance. Cleared after\n // executeTest emits ledger entries for them, so the same instance can\n // be reused across multiple test cases.\n const tracked = new WeakMap<object, HttpInteraction[]>();\n\n const originalAdd = proto.addInteraction as (i: HttpInteraction) => unknown;\n proto.addInteraction = function patchedAddInteraction(\n this: object,\n interaction: HttpInteraction,\n ) {\n const list = tracked.get(this) ?? [];\n list.push(interaction);\n tracked.set(this, list);\n return originalAdd.call(this, interaction);\n };\n\n const originalExecute = proto.executeTest as <T>(\n fn: (server: { url: string; port: number }) => Promise<T>,\n ) => Promise<T | undefined>;\n\n proto.executeTest = async function patchedExecuteTest<T>(\n this: { opts?: { consumer?: string; provider?: string } },\n fn: (server: { url: string; port: number }) => Promise<T>,\n ): Promise<T | undefined> {\n const start = process.hrtime.bigint();\n const consumer = this.opts?.consumer ?? '<unknown>';\n const provider = this.opts?.provider ?? '<unknown>';\n const interactions = tracked.get(this) ?? [];\n tracked.set(this, []);\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'http',\n });\n // If multiple interactions were added before executeTest, the span\n // attributes describe the first; ledger entries are still written\n // for each. Most real tests use one interaction per executeTest.\n const first = interactions[0];\n if (first) {\n span.setAttributes(\n buildPactAttributes({\n consumer,\n provider,\n description: first.uponReceiving,\n states: (first.states ?? []).map((s) => s.description),\n kind: 'http',\n }),\n );\n }\n\n try {\n const result = (await originalExecute.call(this, fn)) as T | undefined;\n span.setAttributes(outcomeAttribute('passed'));\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'passed',\n start,\n });\n }\n return result;\n } catch (error_) {\n span.setAttributes(outcomeAttribute('failed'));\n const error = error_ instanceof Error ? error_.message : String(error_);\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'failed',\n start,\n error,\n });\n }\n throw error_;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction writeAutoLedgerEntry(args: {\n consumer: string;\n provider: string;\n interaction: string;\n states: string[];\n kind: 'message' | 'http';\n outcome: 'passed' | 'failed';\n start: bigint;\n error?: string;\n}): void {\n const ctx = getActiveSpan()?.spanContext();\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: args.consumer,\n provider: args.provider,\n interaction: args.interaction,\n states: args.states,\n kind: args.kind,\n source: 'test',\n role: 'consumer',\n outcome: args.outcome,\n duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,\n observed_at: new Date().toISOString(),\n trace_id: ctx?.traceId,\n span_id: ctx?.spanId,\n run_id: process.env.AUTOTEL_PACT_RUN_ID,\n git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,\n error: args.error,\n };\n appendLedgerEntry(entry);\n}\n\n// Install on import — this file is meant to be loaded once as a setup file.\ninstallAutoWrap();\n"],"mappings":"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"}
|
package/dist/auto-wrap.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-wrap.d.cts","names":[],"sources":["../src/auto-wrap.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"auto-wrap.d.cts","names":[],"sources":["../src/auto-wrap.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAoDU;EACR;IAAwB,WAAW;;EACnC;IAAW,WAAW;;;;;;;;;;;;iBAYR,gBAAgB,aAAa"}
|
package/dist/auto-wrap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-wrap.d.ts","names":[],"sources":["../src/auto-wrap.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"auto-wrap.d.ts","names":[],"sources":["../src/auto-wrap.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAoDU;EACR;IAAwB,WAAW;;EACnC;IAAW,WAAW;;;;;;;;;;;;iBAYR,gBAAgB,aAAa"}
|
package/dist/auto-wrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-wrap.js","names":["autotelSpan"],"sources":["../src/auto-wrap.ts"],"sourcesContent":["/**\n * Auto-wrap entry for vitest / jest setup files.\n *\n * Importing this module monkey-patches `@pact-foundation/pact`'s\n * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`\n * so every contract test in the project records a ledger entry without\n * users having to wrap each call by hand.\n *\n * Usage (vitest):\n *\n * ```ts\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * export default defineConfig({\n * test: { setupFiles: ['autotel-pact/auto-wrap'] },\n * });\n * ```\n *\n * Usage (jest):\n *\n * ```js\n * // jest.config.js\n * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };\n * ```\n *\n * Configuration is via environment variables — there's no API surface,\n * because setup files don't get parameters:\n *\n * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id\n * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir\n *\n * Idempotent: importing twice is a no-op.\n *\n * Safe when Pact-JS isn't installed: logs a single warning and exits\n * cleanly. The same setup file can stay in place for a repo whose\n * non-contract test packages don't have Pact as a dependency.\n */\n\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { createRequire } from 'node:module';\nimport { buildPactAttributes, outcomeAttribute } from './attrs.js';\nimport { appendLedgerEntry } from './ledger.js';\nimport { LEDGER_ENTRY_SPEC, type InteractionLedgerEntry, type PactInteractionMeta } from './types.js';\nimport type { HttpInteraction } from './wrapper-http.js';\nimport type { ReifiedMessage } from './wrapper.js';\n\nconst INSTALLED = Symbol.for('autotel-pact:auto-wrap-installed');\n\ninterface PactJsModule {\n MessageConsumerPact?: { prototype: Record<string | symbol, unknown> };\n PactV3?: { prototype: Record<string | symbol, unknown> };\n}\n\n/**\n * Install the auto-wrap. Called at module load when this file is imported.\n * Exposed for tests + explicit-invocation users.\n *\n * @param pactModule Override Pact-JS resolution. When omitted, requires\n * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.\n *\n * Returns `true` if Pact-JS was found and patched, `false` otherwise.\n */\nexport function installAutoWrap(pactModule?: PactJsModule): boolean {\n const pactJs = pactModule ?? loadPactJs();\n if (!pactJs) return false;\n\n patchMessagePact(pactJs);\n patchHttpPact(pactJs);\n return true;\n}\n\nfunction loadPactJs(): PactJsModule | undefined {\n try {\n // Use createRequire so we work from both ESM and CJS without\n // pulling Pact-JS into the dynamic import graph.\n const require = createRequire(import.meta.url);\n return require('@pact-foundation/pact') as PactJsModule;\n } catch {\n process.stderr.write(\n 'autotel-pact/auto-wrap: @pact-foundation/pact not installed — skipping.\\n',\n );\n return undefined;\n }\n}\n\nfunction patchMessagePact(mod: PactJsModule): void {\n const ctor = mod.MessageConsumerPact;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n const originalVerify = proto.verify as (\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ) => Promise<unknown>;\n\n proto.verify = async function patchedVerify(\n this: { config: { consumer: string; provider: string } },\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ): Promise<unknown> {\n const start = process.hrtime.bigint();\n let captured: ReifiedMessage | undefined;\n const consumer = this.config?.consumer ?? '<unknown>';\n const provider = this.config?.provider ?? '<unknown>';\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'message',\n });\n\n try {\n const result = await originalVerify.call(this, async (reified) => {\n captured = reified;\n const meta: PactInteractionMeta = {\n consumer,\n provider,\n description: reified.description ?? '<unknown>',\n states: (reified.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n };\n span.setAttributes(buildPactAttributes(meta));\n return handler(reified);\n });\n span.setAttributes(outcomeAttribute('passed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'passed',\n start,\n });\n return result;\n } catch (error) {\n span.setAttributes(outcomeAttribute('failed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'failed',\n start,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction patchHttpPact(mod: PactJsModule): void {\n const ctor = mod.PactV3;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n // Track interactions added to each PactV3 instance. Cleared after\n // executeTest emits ledger entries for them, so the same instance can\n // be reused across multiple test cases.\n const tracked = new WeakMap<object, HttpInteraction[]>();\n\n const originalAdd = proto.addInteraction as (i: HttpInteraction) => unknown;\n proto.addInteraction = function patchedAddInteraction(this: object, interaction: HttpInteraction) {\n const list = tracked.get(this) ?? [];\n list.push(interaction);\n tracked.set(this, list);\n return originalAdd.call(this, interaction);\n };\n\n const originalExecute = proto.executeTest as <T>(\n fn: (server: { url: string; port: number }) => Promise<T>,\n ) => Promise<T | undefined>;\n\n proto.executeTest = async function patchedExecuteTest<T>(\n this: { opts?: { consumer?: string; provider?: string } },\n fn: (server: { url: string; port: number }) => Promise<T>,\n ): Promise<T | undefined> {\n const start = process.hrtime.bigint();\n const consumer = this.opts?.consumer ?? '<unknown>';\n const provider = this.opts?.provider ?? '<unknown>';\n const interactions = tracked.get(this) ?? [];\n tracked.set(this, []);\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'http',\n });\n // If multiple interactions were added before executeTest, the span\n // attributes describe the first; ledger entries are still written\n // for each. Most real tests use one interaction per executeTest.\n const first = interactions[0];\n if (first) {\n span.setAttributes(\n buildPactAttributes({\n consumer,\n provider,\n description: first.uponReceiving,\n states: (first.states ?? []).map((s) => s.description),\n kind: 'http',\n }),\n );\n }\n\n try {\n const result = (await originalExecute.call(this, fn)) as T | undefined;\n span.setAttributes(outcomeAttribute('passed'));\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'passed',\n start,\n });\n }\n return result;\n } catch (error_) {\n span.setAttributes(outcomeAttribute('failed'));\n const error = error_ instanceof Error ? error_.message : String(error_);\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'failed',\n start,\n error,\n });\n }\n throw error_;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction writeAutoLedgerEntry(args: {\n consumer: string;\n provider: string;\n interaction: string;\n states: string[];\n kind: 'message' | 'http';\n outcome: 'passed' | 'failed';\n start: bigint;\n error?: string;\n}): void {\n const ctx = getActiveSpan()?.spanContext();\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: args.consumer,\n provider: args.provider,\n interaction: args.interaction,\n states: args.states,\n kind: args.kind,\n source: 'test',\n role: 'consumer',\n outcome: args.outcome,\n duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,\n observed_at: new Date().toISOString(),\n trace_id: ctx?.traceId,\n span_id: ctx?.spanId,\n run_id: process.env.AUTOTEL_PACT_RUN_ID,\n git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,\n error: args.error,\n };\n appendLedgerEntry(entry);\n}\n\n// Install on import — this file is meant to be loaded once as a setup file.\ninstallAutoWrap();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auto-wrap.js","names":["autotelSpan"],"sources":["../src/auto-wrap.ts"],"sourcesContent":["/**\n * Auto-wrap entry for vitest / jest setup files.\n *\n * Importing this module monkey-patches `@pact-foundation/pact`'s\n * `MessageConsumerPact.prototype.verify` and `PactV3.prototype.executeTest`\n * so every contract test in the project records a ledger entry without\n * users having to wrap each call by hand.\n *\n * Usage (vitest):\n *\n * ```ts\n * // vitest.config.ts\n * import { defineConfig } from 'vitest/config';\n * export default defineConfig({\n * test: { setupFiles: ['autotel-pact/auto-wrap'] },\n * });\n * ```\n *\n * Usage (jest):\n *\n * ```js\n * // jest.config.js\n * module.exports = { setupFilesAfterEach: ['autotel-pact/auto-wrap'] };\n * ```\n *\n * Configuration is via environment variables — there's no API surface,\n * because setup files don't get parameters:\n *\n * - `AUTOTEL_PACT_RUN_ID` — tag every ledger entry with this run id\n * - `AUTOTEL_PACT_LEDGER_DIR` — override the default `.autotel-pact/` dir\n *\n * Idempotent: importing twice is a no-op.\n *\n * Safe when Pact-JS isn't installed: logs a single warning and exits\n * cleanly. The same setup file can stay in place for a repo whose\n * non-contract test packages don't have Pact as a dependency.\n */\n\nimport { span as autotelSpan, getActiveSpan } from 'autotel';\nimport { createRequire } from 'node:module';\nimport { buildPactAttributes, outcomeAttribute } from './attrs.js';\nimport { appendLedgerEntry } from './ledger.js';\nimport {\n LEDGER_ENTRY_SPEC,\n type InteractionLedgerEntry,\n type PactInteractionMeta,\n} from './types.js';\nimport type { HttpInteraction } from './wrapper-http.js';\nimport type { ReifiedMessage } from './wrapper.js';\n\nconst INSTALLED = Symbol.for('autotel-pact:auto-wrap-installed');\n\ninterface PactJsModule {\n MessageConsumerPact?: { prototype: Record<string | symbol, unknown> };\n PactV3?: { prototype: Record<string | symbol, unknown> };\n}\n\n/**\n * Install the auto-wrap. Called at module load when this file is imported.\n * Exposed for tests + explicit-invocation users.\n *\n * @param pactModule Override Pact-JS resolution. When omitted, requires\n * `@pact-foundation/pact` from disk. Tests can pass synthetic classes.\n *\n * Returns `true` if Pact-JS was found and patched, `false` otherwise.\n */\nexport function installAutoWrap(pactModule?: PactJsModule): boolean {\n const pactJs = pactModule ?? loadPactJs();\n if (!pactJs) return false;\n\n patchMessagePact(pactJs);\n patchHttpPact(pactJs);\n return true;\n}\n\nfunction loadPactJs(): PactJsModule | undefined {\n try {\n // Use createRequire so we work from both ESM and CJS without\n // pulling Pact-JS into the dynamic import graph.\n const require = createRequire(import.meta.url);\n return require('@pact-foundation/pact') as PactJsModule;\n } catch {\n process.stderr.write(\n 'autotel-pact/auto-wrap: @pact-foundation/pact not installed — skipping.\\n',\n );\n return undefined;\n }\n}\n\nfunction patchMessagePact(mod: PactJsModule): void {\n const ctor = mod.MessageConsumerPact;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n const originalVerify = proto.verify as (\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ) => Promise<unknown>;\n\n proto.verify = async function patchedVerify(\n this: { config: { consumer: string; provider: string } },\n handler: (m: ReifiedMessage) => Promise<unknown>,\n ): Promise<unknown> {\n const start = process.hrtime.bigint();\n let captured: ReifiedMessage | undefined;\n const consumer = this.config?.consumer ?? '<unknown>';\n const provider = this.config?.provider ?? '<unknown>';\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'message',\n });\n\n try {\n const result = await originalVerify.call(this, async (reified) => {\n captured = reified;\n const meta: PactInteractionMeta = {\n consumer,\n provider,\n description: reified.description ?? '<unknown>',\n states: (reified.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n };\n span.setAttributes(buildPactAttributes(meta));\n return handler(reified);\n });\n span.setAttributes(outcomeAttribute('passed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'passed',\n start,\n });\n return result;\n } catch (error) {\n span.setAttributes(outcomeAttribute('failed'));\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: captured?.description ?? '<unknown>',\n states: (captured?.providerStates ?? []).map((s) => s.name),\n kind: 'message',\n outcome: 'failed',\n start,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction patchHttpPact(mod: PactJsModule): void {\n const ctor = mod.PactV3;\n if (!ctor) return;\n const proto = ctor.prototype;\n if (proto[INSTALLED]) return;\n\n // Track interactions added to each PactV3 instance. Cleared after\n // executeTest emits ledger entries for them, so the same instance can\n // be reused across multiple test cases.\n const tracked = new WeakMap<object, HttpInteraction[]>();\n\n const originalAdd = proto.addInteraction as (i: HttpInteraction) => unknown;\n proto.addInteraction = function patchedAddInteraction(\n this: object,\n interaction: HttpInteraction,\n ) {\n const list = tracked.get(this) ?? [];\n list.push(interaction);\n tracked.set(this, list);\n return originalAdd.call(this, interaction);\n };\n\n const originalExecute = proto.executeTest as <T>(\n fn: (server: { url: string; port: number }) => Promise<T>,\n ) => Promise<T | undefined>;\n\n proto.executeTest = async function patchedExecuteTest<T>(\n this: { opts?: { consumer?: string; provider?: string } },\n fn: (server: { url: string; port: number }) => Promise<T>,\n ): Promise<T | undefined> {\n const start = process.hrtime.bigint();\n const consumer = this.opts?.consumer ?? '<unknown>';\n const provider = this.opts?.provider ?? '<unknown>';\n const interactions = tracked.get(this) ?? [];\n tracked.set(this, []);\n\n return autotelSpan('pact.interaction', async (span) => {\n span.setAttributes({\n 'pact.consumer': consumer,\n 'pact.provider': provider,\n 'pact.kind': 'http',\n });\n // If multiple interactions were added before executeTest, the span\n // attributes describe the first; ledger entries are still written\n // for each. Most real tests use one interaction per executeTest.\n const first = interactions[0];\n if (first) {\n span.setAttributes(\n buildPactAttributes({\n consumer,\n provider,\n description: first.uponReceiving,\n states: (first.states ?? []).map((s) => s.description),\n kind: 'http',\n }),\n );\n }\n\n try {\n const result = (await originalExecute.call(this, fn)) as T | undefined;\n span.setAttributes(outcomeAttribute('passed'));\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'passed',\n start,\n });\n }\n return result;\n } catch (error_) {\n span.setAttributes(outcomeAttribute('failed'));\n const error = error_ instanceof Error ? error_.message : String(error_);\n for (const i of interactions) {\n writeAutoLedgerEntry({\n consumer,\n provider,\n interaction: i.uponReceiving,\n states: (i.states ?? []).map((s) => s.description),\n kind: 'http',\n outcome: 'failed',\n start,\n error,\n });\n }\n throw error_;\n }\n });\n };\n\n proto[INSTALLED] = true;\n}\n\nfunction writeAutoLedgerEntry(args: {\n consumer: string;\n provider: string;\n interaction: string;\n states: string[];\n kind: 'message' | 'http';\n outcome: 'passed' | 'failed';\n start: bigint;\n error?: string;\n}): void {\n const ctx = getActiveSpan()?.spanContext();\n const entry: InteractionLedgerEntry = {\n type: 'interaction',\n spec: LEDGER_ENTRY_SPEC,\n consumer: args.consumer,\n provider: args.provider,\n interaction: args.interaction,\n states: args.states,\n kind: args.kind,\n source: 'test',\n role: 'consumer',\n outcome: args.outcome,\n duration_ms: Number(process.hrtime.bigint() - args.start) / 1e6,\n observed_at: new Date().toISOString(),\n trace_id: ctx?.traceId,\n span_id: ctx?.spanId,\n run_id: process.env.AUTOTEL_PACT_RUN_ID,\n git_sha: process.env.GIT_SHA ?? process.env.GITHUB_SHA,\n error: args.error,\n };\n appendLedgerEntry(entry);\n}\n\n// Install on import — this file is meant to be loaded once as a setup file.\ninstallAutoWrap();\n"],"mappings":"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"}
|
package/dist/broker.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.cjs","names":[],"sources":["../src/broker.ts"],"sourcesContent":["import type { BrokerVerification } from './types.js';\n\nexport interface BrokerConfig {\n baseUrl: string;\n token?: string;\n username?: string;\n password?: string;\n}\n\nexport interface ConsumerProviderPair {\n consumer: string;\n provider: string;\n}\n\nfunction authHeaders(config: BrokerConfig): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const encoded = Buffer.from(`${config.username}:${config.password}
|
|
1
|
+
{"version":3,"file":"broker.cjs","names":[],"sources":["../src/broker.ts"],"sourcesContent":["import type { BrokerVerification } from './types.js';\n\nexport interface BrokerConfig {\n baseUrl: string;\n token?: string;\n username?: string;\n password?: string;\n}\n\nexport interface ConsumerProviderPair {\n consumer: string;\n provider: string;\n}\n\nfunction authHeaders(config: BrokerConfig): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const encoded = Buffer.from(\n `${config.username}:${config.password}`,\n ).toString('base64');\n headers.Authorization = `Basic ${encoded}`;\n }\n return headers;\n}\n\nfunction trimBaseUrl(url: string): string {\n return url.replace(/\\/$/, '');\n}\n\n/**\n * Parse Pact Broker latest verification result payload.\n */\nexport function parseBrokerVerificationResult(\n consumer: string,\n provider: string,\n json: unknown,\n): BrokerVerification | null {\n if (!json || typeof json !== 'object') return null;\n const body = json as Record<string, unknown>;\n const success =\n body.success === true ||\n (body.success === undefined && body.result === 'success');\n const verifiedAt =\n typeof body.verifiedAt === 'string'\n ? body.verifiedAt\n : typeof body.verified_at === 'string'\n ? body.verified_at\n : typeof body.createdAt === 'string'\n ? body.createdAt\n : undefined;\n\n return {\n consumer,\n provider,\n success: !!success,\n verifiedAt,\n };\n}\n\n/**\n * Fetch latest verification results for each consumer–provider pair.\n */\nexport async function fetchBrokerVerifications(\n config: BrokerConfig,\n pairs: ConsumerProviderPair[],\n): Promise<BrokerVerification[]> {\n const base = trimBaseUrl(config.baseUrl);\n const headers = authHeaders(config);\n const results: BrokerVerification[] = [];\n\n for (const { consumer, provider } of pairs) {\n const url = `${base}/pacts/provider/${encodeURIComponent(provider)}/consumer/${encodeURIComponent(consumer)}/latest/verification-results`;\n try {\n const res = await fetch(url, { headers });\n if (!res.ok) {\n results.push({\n consumer,\n provider,\n success: false,\n error: `HTTP ${res.status} ${res.statusText}`.trim(),\n });\n continue;\n }\n const json: unknown = await res.json();\n const parsed = parseBrokerVerificationResult(consumer, provider, json);\n results.push(\n parsed ?? {\n consumer,\n provider,\n success: false,\n error: 'Unparseable broker response',\n },\n );\n } catch (error) {\n results.push({\n consumer,\n provider,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return results;\n}\n\nexport function brokerConfigFromEnv(): BrokerConfig | undefined {\n const baseUrl = process.env.PACT_BROKER_BASE_URL;\n if (!baseUrl) return undefined;\n return {\n baseUrl,\n token: process.env.PACT_BROKER_TOKEN,\n username: process.env.PACT_BROKER_USERNAME,\n password: process.env.PACT_BROKER_PASSWORD,\n };\n}\n"],"mappings":";;;AAcA,SAAS,YAAY,QAA8C;CACjE,MAAM,UAAkC,EACtC,QAAQ,mBACV;CACA,IAAI,OAAO,OACT,QAAQ,gBAAgB,UAAU,OAAO;MACpC,IAAI,OAAO,YAAY,OAAO,UAInC,QAAQ,gBAAgB,SAHR,OAAO,KACrB,GAAG,OAAO,SAAS,GAAG,OAAO,UAC/B,CAAC,CAAC,SAAS,QAC4B;CAEzC,OAAO;AACT;AAEA,SAAS,YAAY,KAAqB;CACxC,OAAO,IAAI,QAAQ,OAAO,EAAE;AAC9B;;;;AAKA,SAAgB,8BACd,UACA,UACA,MAC2B;CAC3B,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,OAAO;CACb,MAAM,UACJ,KAAK,YAAY,QAChB,KAAK,YAAY,UAAa,KAAK,WAAW;CACjD,MAAM,aACJ,OAAO,KAAK,eAAe,WACvB,KAAK,aACL,OAAO,KAAK,gBAAgB,WAC1B,KAAK,cACL,OAAO,KAAK,cAAc,WACxB,KAAK,YACL;CAEV,OAAO;EACL;EACA;EACA,SAAS,CAAC,CAAC;EACX;CACF;AACF;;;;AAKA,eAAsB,yBACpB,QACA,OAC+B;CAC/B,MAAM,OAAO,YAAY,OAAO,OAAO;CACvC,MAAM,UAAU,YAAY,MAAM;CAClC,MAAM,UAAgC,CAAC;CAEvC,KAAK,MAAM,EAAE,UAAU,cAAc,OAAO;EAC1C,MAAM,MAAM,GAAG,KAAK,kBAAkB,mBAAmB,QAAQ,EAAE,YAAY,mBAAmB,QAAQ,EAAE;EAC5G,IAAI;GACF,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;GACxC,IAAI,CAAC,IAAI,IAAI;IACX,QAAQ,KAAK;KACX;KACA;KACA,SAAS;KACT,OAAO,QAAQ,IAAI,OAAO,GAAG,IAAI,aAAa,KAAK;IACrD,CAAC;IACD;GACF;GAEA,MAAM,SAAS,8BAA8B,UAAU,UAAU,MADrC,IAAI,KAAK,CACgC;GACrE,QAAQ,KACN,UAAU;IACR;IACA;IACA,SAAS;IACT,OAAO;GACT,CACF;EACF,SAAS,OAAO;GACd,QAAQ,KAAK;IACX;IACA;IACA,SAAS;IACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBAAgD;CAC9D,MAAM,UAAU,QAAQ,IAAI;CAC5B,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL;EACA,OAAO,QAAQ,IAAI;EACnB,UAAU,QAAQ,IAAI;EACtB,UAAU,QAAQ,IAAI;CACxB;AACF"}
|
package/dist/broker.d.cts
CHANGED
package/dist/broker.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.d.cts","names":[],"sources":["../src/broker.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"broker.d.cts","names":[],"sources":["../src/broker.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;;;;;iBAyBc,8BACd,kBACA,kBACA,gBACC;;;;iBA0BmB,yBACpB,QAAQ,cACR,OAAO,yBACN,QAAQ;iBAyCK,uBAAuB"}
|
package/dist/broker.d.ts
CHANGED
package/dist/broker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.d.ts","names":[],"sources":["../src/broker.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"broker.d.ts","names":[],"sources":["../src/broker.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;;;;;iBAyBc,8BACd,kBACA,kBACA,gBACC;;;;iBA0BmB,yBACpB,QAAQ,cACR,OAAO,yBACN,QAAQ;iBAyCK,uBAAuB"}
|
package/dist/broker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broker.js","names":[],"sources":["../src/broker.ts"],"sourcesContent":["import type { BrokerVerification } from './types.js';\n\nexport interface BrokerConfig {\n baseUrl: string;\n token?: string;\n username?: string;\n password?: string;\n}\n\nexport interface ConsumerProviderPair {\n consumer: string;\n provider: string;\n}\n\nfunction authHeaders(config: BrokerConfig): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const encoded = Buffer.from(`${config.username}:${config.password}
|
|
1
|
+
{"version":3,"file":"broker.js","names":[],"sources":["../src/broker.ts"],"sourcesContent":["import type { BrokerVerification } from './types.js';\n\nexport interface BrokerConfig {\n baseUrl: string;\n token?: string;\n username?: string;\n password?: string;\n}\n\nexport interface ConsumerProviderPair {\n consumer: string;\n provider: string;\n}\n\nfunction authHeaders(config: BrokerConfig): Record<string, string> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.password) {\n const encoded = Buffer.from(\n `${config.username}:${config.password}`,\n ).toString('base64');\n headers.Authorization = `Basic ${encoded}`;\n }\n return headers;\n}\n\nfunction trimBaseUrl(url: string): string {\n return url.replace(/\\/$/, '');\n}\n\n/**\n * Parse Pact Broker latest verification result payload.\n */\nexport function parseBrokerVerificationResult(\n consumer: string,\n provider: string,\n json: unknown,\n): BrokerVerification | null {\n if (!json || typeof json !== 'object') return null;\n const body = json as Record<string, unknown>;\n const success =\n body.success === true ||\n (body.success === undefined && body.result === 'success');\n const verifiedAt =\n typeof body.verifiedAt === 'string'\n ? body.verifiedAt\n : typeof body.verified_at === 'string'\n ? body.verified_at\n : typeof body.createdAt === 'string'\n ? body.createdAt\n : undefined;\n\n return {\n consumer,\n provider,\n success: !!success,\n verifiedAt,\n };\n}\n\n/**\n * Fetch latest verification results for each consumer–provider pair.\n */\nexport async function fetchBrokerVerifications(\n config: BrokerConfig,\n pairs: ConsumerProviderPair[],\n): Promise<BrokerVerification[]> {\n const base = trimBaseUrl(config.baseUrl);\n const headers = authHeaders(config);\n const results: BrokerVerification[] = [];\n\n for (const { consumer, provider } of pairs) {\n const url = `${base}/pacts/provider/${encodeURIComponent(provider)}/consumer/${encodeURIComponent(consumer)}/latest/verification-results`;\n try {\n const res = await fetch(url, { headers });\n if (!res.ok) {\n results.push({\n consumer,\n provider,\n success: false,\n error: `HTTP ${res.status} ${res.statusText}`.trim(),\n });\n continue;\n }\n const json: unknown = await res.json();\n const parsed = parseBrokerVerificationResult(consumer, provider, json);\n results.push(\n parsed ?? {\n consumer,\n provider,\n success: false,\n error: 'Unparseable broker response',\n },\n );\n } catch (error) {\n results.push({\n consumer,\n provider,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return results;\n}\n\nexport function brokerConfigFromEnv(): BrokerConfig | undefined {\n const baseUrl = process.env.PACT_BROKER_BASE_URL;\n if (!baseUrl) return undefined;\n return {\n baseUrl,\n token: process.env.PACT_BROKER_TOKEN,\n username: process.env.PACT_BROKER_USERNAME,\n password: process.env.PACT_BROKER_PASSWORD,\n };\n}\n"],"mappings":";AAcA,SAAS,YAAY,QAA8C;CACjE,MAAM,UAAkC,EACtC,QAAQ,mBACV;CACA,IAAI,OAAO,OACT,QAAQ,gBAAgB,UAAU,OAAO;MACpC,IAAI,OAAO,YAAY,OAAO,UAInC,QAAQ,gBAAgB,SAHR,OAAO,KACrB,GAAG,OAAO,SAAS,GAAG,OAAO,UAC/B,CAAC,CAAC,SAAS,QAC4B;CAEzC,OAAO;AACT;AAEA,SAAS,YAAY,KAAqB;CACxC,OAAO,IAAI,QAAQ,OAAO,EAAE;AAC9B;;;;AAKA,SAAgB,8BACd,UACA,UACA,MAC2B;CAC3B,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,OAAO;CACb,MAAM,UACJ,KAAK,YAAY,QAChB,KAAK,YAAY,UAAa,KAAK,WAAW;CACjD,MAAM,aACJ,OAAO,KAAK,eAAe,WACvB,KAAK,aACL,OAAO,KAAK,gBAAgB,WAC1B,KAAK,cACL,OAAO,KAAK,cAAc,WACxB,KAAK,YACL;CAEV,OAAO;EACL;EACA;EACA,SAAS,CAAC,CAAC;EACX;CACF;AACF;;;;AAKA,eAAsB,yBACpB,QACA,OAC+B;CAC/B,MAAM,OAAO,YAAY,OAAO,OAAO;CACvC,MAAM,UAAU,YAAY,MAAM;CAClC,MAAM,UAAgC,CAAC;CAEvC,KAAK,MAAM,EAAE,UAAU,cAAc,OAAO;EAC1C,MAAM,MAAM,GAAG,KAAK,kBAAkB,mBAAmB,QAAQ,EAAE,YAAY,mBAAmB,QAAQ,EAAE;EAC5G,IAAI;GACF,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,CAAC;GACxC,IAAI,CAAC,IAAI,IAAI;IACX,QAAQ,KAAK;KACX;KACA;KACA,SAAS;KACT,OAAO,QAAQ,IAAI,OAAO,GAAG,IAAI,aAAa,KAAK;IACrD,CAAC;IACD;GACF;GAEA,MAAM,SAAS,8BAA8B,UAAU,UAAU,MADrC,IAAI,KAAK,CACgC;GACrE,QAAQ,KACN,UAAU;IACR;IACA;IACA,SAAS;IACT,OAAO;GACT,CACF;EACF,SAAS,OAAO;GACd,QAAQ,KAAK;IACX;IACA;IACA,SAAS;IACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D,CAAC;EACH;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sBAAgD;CAC9D,MAAM,UAAU,QAAQ,IAAI;CAC5B,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EACL;EACA,OAAO,QAAQ,IAAI;EACnB,UAAU,QAAQ,IAAI;EACtB,UAAU,QAAQ,IAAI;CACxB;AACF"}
|