bruno-lifecycle-adapter 0.1.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 +403 -0
- package/dist/index.d.mts +254 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +668 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +638 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# bruno-lifecycle-adapter
|
|
2
|
+
|
|
3
|
+
A TypeScript package that wraps [Bruno CLI](https://www.usebruno.com/) and exposes an observable execution lifecycle for external integrations.
|
|
4
|
+
|
|
5
|
+
It runs Bruno collections through `bru run`, captures process output, parses generated reports, and emits typed lifecycle events such as run start/end, request execution, test results, assertions, stdout, and stderr.
|
|
6
|
+
|
|
7
|
+
The adapter is designed to be **independent from Bruno's core**, with a clean architecture, strong typing, automated tests, and a stable internal contract that can evolve even if Bruno's report format changes over time.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install bruno-lifecycle-adapter
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Bruno CLI must be installed separately (the adapter calls `bru run`):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @usebruno/cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { BrunoLifecycleAdapter } from 'bruno-lifecycle-adapter';
|
|
29
|
+
|
|
30
|
+
const adapter = new BrunoLifecycleAdapter();
|
|
31
|
+
|
|
32
|
+
adapter.on('run:started', (event) => {
|
|
33
|
+
console.log(`Run started (pid ${event.pid}), runId: ${event.runId}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
adapter.on('request:finished', (event) => {
|
|
37
|
+
console.log(`${event.requestName} → ${event.status} (${event.responseStatus ?? '?'})`);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
adapter.on('test:finished', (event) => {
|
|
41
|
+
console.log(` test "${event.testName}": ${event.status}`);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
adapter.on('assertion:result', (event) => {
|
|
45
|
+
console.log(` assertion "${event.assertion}": ${event.passed ? 'pass' : 'fail'}`);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const summary = await adapter.run({
|
|
49
|
+
cwd: process.cwd(),
|
|
50
|
+
collectionPath: './my-collection',
|
|
51
|
+
env: 'local',
|
|
52
|
+
reporterJsonPath: './tmp/bruno-report.json',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log('Exit code:', summary.exitCode);
|
|
56
|
+
console.log('Passed requests:', summary.passedRequests, '/', summary.totalRequests);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
interface AdapterRunConfig {
|
|
65
|
+
/** Working directory where `bru run` is executed. */
|
|
66
|
+
cwd: string;
|
|
67
|
+
|
|
68
|
+
/** Path to the Bruno collection directory or specific `.bru` file. */
|
|
69
|
+
collectionPath: string;
|
|
70
|
+
|
|
71
|
+
/** Environment name passed to `--env`. */
|
|
72
|
+
env?: string;
|
|
73
|
+
|
|
74
|
+
/** Path where Bruno writes its JSON report (`--reporter-json`). */
|
|
75
|
+
reporterJsonPath?: string;
|
|
76
|
+
|
|
77
|
+
/** When true, passes `-r` to `bru run` to recurse into sub-folders. */
|
|
78
|
+
recursive?: boolean;
|
|
79
|
+
|
|
80
|
+
/** Extra CLI arguments appended verbatim after the collection path. */
|
|
81
|
+
extraArgs?: string[];
|
|
82
|
+
|
|
83
|
+
/** Path to the `bru` binary. Defaults to `bru` (resolved from PATH). */
|
|
84
|
+
bruBin?: string;
|
|
85
|
+
|
|
86
|
+
/** Timeout in milliseconds for the entire run. `0` means no timeout. */
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Event Subscription
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// Subscribe and get an unsubscribe handle
|
|
97
|
+
const unsubscribe = adapter.on('stdout', (event) => {
|
|
98
|
+
process.stdout.write(event.chunk);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Subscribe once – fires only on the first emission
|
|
102
|
+
adapter.once('run:finished', (event) => {
|
|
103
|
+
console.log('Total duration:', event.durationMs, 'ms');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Unsubscribe manually
|
|
107
|
+
adapter.off('stderr', myStderrHandler);
|
|
108
|
+
|
|
109
|
+
// Or use the returned unsubscribe function
|
|
110
|
+
unsubscribe();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Lifecycle Events
|
|
116
|
+
|
|
117
|
+
| Event | Reliability | Description |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `run:starting` | native | Emitted before spawning the process |
|
|
120
|
+
| `run:started` | native | Emitted once the child process has a PID |
|
|
121
|
+
| `run:finished` | native | Emitted when the process exits with code 0 |
|
|
122
|
+
| `run:failed` | native | Emitted when the process exits with non-zero code, times out, or fails to spawn |
|
|
123
|
+
| `request:discovered` | inferred | Emitted per `.bru` request file found in the collection directory, fired after the process exits and before report-derived events |
|
|
124
|
+
| `request:started` | derived | Emitted immediately before each `request:finished` or `request:skipped` (derived from the JSON report; fires in batch after the run) |
|
|
125
|
+
| `request:finished` | derived | Emitted per non-skipped request after successful report parsing |
|
|
126
|
+
| `request:skipped` | derived | Emitted per skipped request after successful report parsing |
|
|
127
|
+
| `test:started` | derived | Emitted immediately before each `test:finished` (derived from the JSON report; fires in batch after the run) |
|
|
128
|
+
| `test:finished` | derived | Emitted per test result after successful report parsing |
|
|
129
|
+
| `assertion:result` | derived | Emitted per assertion after successful report parsing |
|
|
130
|
+
| `stdout` | native | Raw stdout chunk from the `bru run` process |
|
|
131
|
+
| `stderr` | native | Raw stderr chunk from the `bru run` process |
|
|
132
|
+
| `report:json:ready` | derived | Emitted only after the JSON report is parsed successfully |
|
|
133
|
+
|
|
134
|
+
### Event Reliability
|
|
135
|
+
|
|
136
|
+
Each event payload includes a `reliability` field:
|
|
137
|
+
|
|
138
|
+
- **`native`** – directly observed from process stdout/stderr or exit code
|
|
139
|
+
- **`derived`** – computed from one or more native signals (e.g. parsed report data)
|
|
140
|
+
- **`inferred`** – best-effort; could not be confirmed from available signals
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Event Payloads
|
|
145
|
+
|
|
146
|
+
All event payloads extend `EventMetadata`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
interface EventMetadata {
|
|
150
|
+
runId: string; // UUID identifying this run
|
|
151
|
+
timestamp: string; // ISO 8601 timestamp
|
|
152
|
+
reliability: 'native' | 'derived' | 'inferred';
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Example payloads:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
interface RunStartedEvent extends EventMetadata {
|
|
160
|
+
event: 'run:started';
|
|
161
|
+
cwd: string;
|
|
162
|
+
collectionPath: string;
|
|
163
|
+
pid: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface RequestFinishedEvent extends EventMetadata {
|
|
167
|
+
event: 'request:finished';
|
|
168
|
+
requestName: string;
|
|
169
|
+
requestFile: string;
|
|
170
|
+
status: 'discovered' | 'started' | 'finished' | 'skipped' | 'failed';
|
|
171
|
+
responseStatus?: number;
|
|
172
|
+
durationMs?: number;
|
|
173
|
+
error?: { message: string; stack?: string };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface TestFinishedEvent extends EventMetadata {
|
|
177
|
+
event: 'test:finished';
|
|
178
|
+
requestName: string;
|
|
179
|
+
testName: string;
|
|
180
|
+
status: 'started' | 'passed' | 'failed' | 'skipped';
|
|
181
|
+
error?: { message: string; stack?: string };
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Execution Summary
|
|
188
|
+
|
|
189
|
+
`adapter.run()` resolves with an `ExecutionSummary`:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
interface ExecutionSummary {
|
|
193
|
+
runId: string;
|
|
194
|
+
collectionPath: string;
|
|
195
|
+
startedAt: string;
|
|
196
|
+
finishedAt: string;
|
|
197
|
+
durationMs: number;
|
|
198
|
+
exitCode: number;
|
|
199
|
+
status: 'starting' | 'running' | 'finished' | 'failed' | 'cancelled';
|
|
200
|
+
totalRequests: number;
|
|
201
|
+
passedRequests: number;
|
|
202
|
+
failedRequests: number;
|
|
203
|
+
skippedRequests: number;
|
|
204
|
+
totalTests: number;
|
|
205
|
+
passedTests: number;
|
|
206
|
+
failedTests: number;
|
|
207
|
+
totalAssertions: number;
|
|
208
|
+
passedAssertions: number;
|
|
209
|
+
failedAssertions: number;
|
|
210
|
+
requests: RequestResult[];
|
|
211
|
+
error?: ErrorDetail;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Custom Report Parser
|
|
218
|
+
|
|
219
|
+
You can supply your own report parser to handle custom Bruno report formats:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { BrunoLifecycleAdapter } from 'bruno-lifecycle-adapter';
|
|
223
|
+
import type { ReportParserContract } from 'bruno-lifecycle-adapter';
|
|
224
|
+
|
|
225
|
+
class MyCustomParser implements ReportParserContract {
|
|
226
|
+
async parse(reportPath: string): Promise<ExecutionSummary> {
|
|
227
|
+
// read and map your format
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const adapter = new BrunoLifecycleAdapter(new MyCustomParser());
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Limitations
|
|
237
|
+
|
|
238
|
+
- **No native per-request events from process stdout.** Bruno CLI does not emit structured events to stdout; per-request and per-test events are derived from the JSON report. Subscribe to `stdout` for raw output.
|
|
239
|
+
- **Report events require `reporterJsonPath`.** Without a JSON report path, `request:finished`, `test:finished`, `assertion:result`, and `report:json:ready` are not emitted.
|
|
240
|
+
- **Bruno JSON schema may change.** The parser maps known fields and falls back gracefully, but new Bruno versions may change the report schema. Inspect the raw `stdout` events if you need forward compatibility.
|
|
241
|
+
- **Timeout kills the process with `SIGTERM`.** The process is sent `SIGTERM` on timeout; clean shutdown depends on the OS and Bruno's signal handling.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Project Structure
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
src/
|
|
249
|
+
domain/
|
|
250
|
+
events.ts # All lifecycle event types and execution models
|
|
251
|
+
models.ts # AdapterRunConfig and ReportParserContract interfaces
|
|
252
|
+
application/
|
|
253
|
+
BrunoLifecycleAdapter.ts # Main adapter (spawns bru, emits events)
|
|
254
|
+
infrastructure/
|
|
255
|
+
TypedEventBus.ts # Strongly-typed event bus
|
|
256
|
+
BrunoJsonReportParser.ts # Bruno JSON report → internal model
|
|
257
|
+
shared/
|
|
258
|
+
utils.ts # generateRunId, nowIso, elapsedMs
|
|
259
|
+
index.ts # Public exports
|
|
260
|
+
tests/
|
|
261
|
+
unit/
|
|
262
|
+
TypedEventBus.test.ts
|
|
263
|
+
BrunoJsonReportParser.test.ts
|
|
264
|
+
BrunoLifecycleAdapter.test.ts
|
|
265
|
+
BruFileScanner.test.ts
|
|
266
|
+
integration/
|
|
267
|
+
adapter.integration.test.ts # End-to-end integration tests
|
|
268
|
+
fixtures/
|
|
269
|
+
bruno-project/ # Sample Bruno collection
|
|
270
|
+
bruno.json
|
|
271
|
+
environments/local.bru
|
|
272
|
+
health-check.bru
|
|
273
|
+
echo-post.bru
|
|
274
|
+
multi-request-flow/
|
|
275
|
+
01-list-items.bru
|
|
276
|
+
02-get-item-by-id.bru
|
|
277
|
+
external-listener/ # External JS consumer package
|
|
278
|
+
package.json
|
|
279
|
+
index.js
|
|
280
|
+
e2e/
|
|
281
|
+
app-server.js # Standalone HTTP server for Docker E2E
|
|
282
|
+
run.js # Docker E2E test runner entry-point
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Development
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npm install
|
|
291
|
+
npm run build # compile with tsup
|
|
292
|
+
npm test # run unit tests with vitest
|
|
293
|
+
npm run test:unit # explicit alias for unit tests
|
|
294
|
+
npm run test:e2e # run full E2E suite inside Docker Compose
|
|
295
|
+
npm run typecheck # tsc --noEmit
|
|
296
|
+
npm run lint # eslint
|
|
297
|
+
npm run format # prettier --write
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Integration Tests
|
|
303
|
+
|
|
304
|
+
Integration tests verify the adapter against a real Bruno installation.
|
|
305
|
+
|
|
306
|
+
### Prerequisites
|
|
307
|
+
|
|
308
|
+
1. Build the adapter:
|
|
309
|
+
```bash
|
|
310
|
+
npm run build
|
|
311
|
+
```
|
|
312
|
+
2. Install Bruno CLI:
|
|
313
|
+
```bash
|
|
314
|
+
npm install -g @usebruno/cli
|
|
315
|
+
```
|
|
316
|
+
3. Install external listener dependencies:
|
|
317
|
+
```bash
|
|
318
|
+
npm run test:integration:setup
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Running integration tests
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
npm run test:integration
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Full CI-equivalent local run
|
|
328
|
+
|
|
329
|
+
This command reproduces the exact CI execution sequence:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npm run ci:local
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
It runs: build → lint → typecheck → unit tests → integration setup → integration tests.
|
|
336
|
+
|
|
337
|
+
> **Note:** Integration tests **fail** (they do not skip) when `bru` is not
|
|
338
|
+
> found on PATH. This is intentional — a passing run without Bruno would give
|
|
339
|
+
> false confidence. Unit tests (`npm test`) run without Bruno.
|
|
340
|
+
|
|
341
|
+
### How integration tests work
|
|
342
|
+
|
|
343
|
+
1. A minimal HTTP server is started on port `47891` to serve the Bruno
|
|
344
|
+
collection's requests locally — no external network calls required.
|
|
345
|
+
2. The **external listener** (`tests/integration/fixtures/external-listener/`)
|
|
346
|
+
is spawned as a child process. It imports the adapter, subscribes to all
|
|
347
|
+
lifecycle events, and writes structured JSON-lines to stdout for each event.
|
|
348
|
+
3. The integration test captures the listener's stdout and asserts that
|
|
349
|
+
expected events were logged — confirming that the adapter correctly surfaces
|
|
350
|
+
lifecycle information to external consumers.
|
|
351
|
+
|
|
352
|
+
### CI
|
|
353
|
+
|
|
354
|
+
Integration tests run automatically on **pull requests targeting `main`** via
|
|
355
|
+
the `.github/workflows/integration.yml` workflow. They do not run on feature
|
|
356
|
+
branch pushes to keep CI fast.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## E2E Tests
|
|
361
|
+
|
|
362
|
+
E2E tests verify the adapter end-to-end inside isolated Docker containers —
|
|
363
|
+
no host-level Bruno installation is required.
|
|
364
|
+
|
|
365
|
+
### Running E2E tests
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
docker compose up --build --exit-code-from e2e --abort-on-container-exit
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Or via the npm script:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
npm run test:e2e
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### How E2E tests work
|
|
378
|
+
|
|
379
|
+
The `docker-compose.yml` starts two services:
|
|
380
|
+
|
|
381
|
+
1. **`app`** (`node:20-alpine`): a minimal HTTP server exposing the same routes
|
|
382
|
+
as the integration test server (`/health`, `/echo`, `/items`, `/items/1`).
|
|
383
|
+
Reachable within the Compose network as `http://app:47891`.
|
|
384
|
+
2. **`e2e`** (built from `Dockerfile`): installs Bruno CLI globally, resolves
|
|
385
|
+
the adapter from the local `dist/`, installs the external listener, and
|
|
386
|
+
runs `tests/e2e/run.js`.
|
|
387
|
+
|
|
388
|
+
The `e2e` container exits 0 on success, 1 on failure.
|
|
389
|
+
`docker compose up --exit-code-from e2e` propagates that exit code to the host.
|
|
390
|
+
|
|
391
|
+
### CI
|
|
392
|
+
|
|
393
|
+
E2E tests run automatically on **pull requests targeting `main`** via the
|
|
394
|
+
`.github/workflows/e2e.yml` workflow. Two jobs run in sequence:
|
|
395
|
+
|
|
396
|
+
1. `docker-build` — verifies the `Dockerfile` builds successfully.
|
|
397
|
+
2. `e2e` — runs `docker compose up` with the full stack.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## License
|
|
402
|
+
|
|
403
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indicates how the event was produced relative to Bruno's native output.
|
|
3
|
+
* - `native` – directly observed from process stdout/stderr or exit code
|
|
4
|
+
* - `derived` – computed from one or more native signals
|
|
5
|
+
* - `inferred`– best-effort, could not be confirmed from available signals
|
|
6
|
+
*/
|
|
7
|
+
type EventReliability = 'native' | 'derived' | 'inferred';
|
|
8
|
+
type RunStatus = 'starting' | 'running' | 'finished' | 'failed' | 'cancelled';
|
|
9
|
+
type RequestStatus = 'discovered' | 'started' | 'finished' | 'skipped' | 'failed';
|
|
10
|
+
type TestStatus = 'started' | 'passed' | 'failed' | 'skipped';
|
|
11
|
+
interface ErrorDetail {
|
|
12
|
+
message: string;
|
|
13
|
+
stack?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
interface EventMetadata {
|
|
16
|
+
runId: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
reliability: EventReliability;
|
|
19
|
+
}
|
|
20
|
+
interface RunStartingEvent extends EventMetadata {
|
|
21
|
+
event: 'run:starting';
|
|
22
|
+
cwd: string;
|
|
23
|
+
collectionPath: string;
|
|
24
|
+
}
|
|
25
|
+
interface RunStartedEvent extends EventMetadata {
|
|
26
|
+
event: 'run:started';
|
|
27
|
+
cwd: string;
|
|
28
|
+
collectionPath: string;
|
|
29
|
+
pid: number;
|
|
30
|
+
}
|
|
31
|
+
interface RunFinishedEvent extends EventMetadata {
|
|
32
|
+
event: 'run:finished';
|
|
33
|
+
exitCode: number;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
summary: ExecutionSummary;
|
|
36
|
+
}
|
|
37
|
+
interface RunFailedEvent extends EventMetadata {
|
|
38
|
+
event: 'run:failed';
|
|
39
|
+
exitCode: number;
|
|
40
|
+
durationMs: number;
|
|
41
|
+
error: ErrorDetail;
|
|
42
|
+
summary: ExecutionSummary;
|
|
43
|
+
}
|
|
44
|
+
interface RequestDiscoveredEvent extends EventMetadata {
|
|
45
|
+
event: 'request:discovered';
|
|
46
|
+
requestName: string;
|
|
47
|
+
requestFile: string;
|
|
48
|
+
}
|
|
49
|
+
interface RequestStartedEvent extends EventMetadata {
|
|
50
|
+
event: 'request:started';
|
|
51
|
+
requestName: string;
|
|
52
|
+
requestFile: string;
|
|
53
|
+
}
|
|
54
|
+
interface RequestFinishedEvent extends EventMetadata {
|
|
55
|
+
event: 'request:finished';
|
|
56
|
+
requestName: string;
|
|
57
|
+
requestFile: string;
|
|
58
|
+
status: RequestStatus;
|
|
59
|
+
responseStatus?: number | undefined;
|
|
60
|
+
durationMs?: number | undefined;
|
|
61
|
+
error?: ErrorDetail | undefined;
|
|
62
|
+
}
|
|
63
|
+
interface RequestSkippedEvent extends EventMetadata {
|
|
64
|
+
event: 'request:skipped';
|
|
65
|
+
requestName: string;
|
|
66
|
+
requestFile: string;
|
|
67
|
+
}
|
|
68
|
+
interface TestStartedEvent extends EventMetadata {
|
|
69
|
+
event: 'test:started';
|
|
70
|
+
requestName: string;
|
|
71
|
+
testName: string;
|
|
72
|
+
}
|
|
73
|
+
interface TestFinishedEvent extends EventMetadata {
|
|
74
|
+
event: 'test:finished';
|
|
75
|
+
requestName: string;
|
|
76
|
+
testName: string;
|
|
77
|
+
status: TestStatus;
|
|
78
|
+
error?: ErrorDetail | undefined;
|
|
79
|
+
}
|
|
80
|
+
interface AssertionResultEvent extends EventMetadata {
|
|
81
|
+
event: 'assertion:result';
|
|
82
|
+
requestName: string;
|
|
83
|
+
assertion: string;
|
|
84
|
+
passed: boolean;
|
|
85
|
+
actual?: unknown;
|
|
86
|
+
expected?: unknown;
|
|
87
|
+
error?: ErrorDetail | undefined;
|
|
88
|
+
}
|
|
89
|
+
interface StdoutEvent extends EventMetadata {
|
|
90
|
+
event: 'stdout';
|
|
91
|
+
chunk: string;
|
|
92
|
+
}
|
|
93
|
+
interface StderrEvent extends EventMetadata {
|
|
94
|
+
event: 'stderr';
|
|
95
|
+
chunk: string;
|
|
96
|
+
}
|
|
97
|
+
interface ReportJsonReadyEvent extends EventMetadata {
|
|
98
|
+
event: 'report:json:ready';
|
|
99
|
+
path: string;
|
|
100
|
+
summary: ExecutionSummary;
|
|
101
|
+
}
|
|
102
|
+
type LifecycleEvent = RunStartingEvent | RunStartedEvent | RunFinishedEvent | RunFailedEvent | RequestDiscoveredEvent | RequestStartedEvent | RequestFinishedEvent | RequestSkippedEvent | TestStartedEvent | TestFinishedEvent | AssertionResultEvent | StdoutEvent | StderrEvent | ReportJsonReadyEvent;
|
|
103
|
+
type LifecycleEventName = LifecycleEvent['event'];
|
|
104
|
+
type EventPayload<T extends LifecycleEventName> = Extract<LifecycleEvent, {
|
|
105
|
+
event: T;
|
|
106
|
+
}>;
|
|
107
|
+
interface AssertionResult {
|
|
108
|
+
assertion: string;
|
|
109
|
+
passed: boolean;
|
|
110
|
+
actual?: unknown;
|
|
111
|
+
expected?: unknown;
|
|
112
|
+
error?: ErrorDetail | undefined;
|
|
113
|
+
}
|
|
114
|
+
interface TestResult {
|
|
115
|
+
testName: string;
|
|
116
|
+
status: TestStatus;
|
|
117
|
+
error?: ErrorDetail | undefined;
|
|
118
|
+
}
|
|
119
|
+
interface RequestResult {
|
|
120
|
+
requestName: string;
|
|
121
|
+
requestFile: string;
|
|
122
|
+
status: RequestStatus;
|
|
123
|
+
responseStatus?: number | undefined;
|
|
124
|
+
durationMs?: number | undefined;
|
|
125
|
+
tests: TestResult[];
|
|
126
|
+
assertions: AssertionResult[];
|
|
127
|
+
error?: ErrorDetail | undefined;
|
|
128
|
+
}
|
|
129
|
+
interface ExecutionSummary {
|
|
130
|
+
runId: string;
|
|
131
|
+
collectionPath: string;
|
|
132
|
+
startedAt: string;
|
|
133
|
+
finishedAt: string;
|
|
134
|
+
durationMs: number;
|
|
135
|
+
exitCode: number;
|
|
136
|
+
status: RunStatus;
|
|
137
|
+
totalRequests: number;
|
|
138
|
+
passedRequests: number;
|
|
139
|
+
failedRequests: number;
|
|
140
|
+
skippedRequests: number;
|
|
141
|
+
totalTests: number;
|
|
142
|
+
passedTests: number;
|
|
143
|
+
failedTests: number;
|
|
144
|
+
totalAssertions: number;
|
|
145
|
+
passedAssertions: number;
|
|
146
|
+
failedAssertions: number;
|
|
147
|
+
requests: RequestResult[];
|
|
148
|
+
error?: ErrorDetail | undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface AdapterRunConfig {
|
|
152
|
+
/** Working directory where `bru run` is executed. */
|
|
153
|
+
cwd: string;
|
|
154
|
+
/** Path to the Bruno collection directory or specific `.bru` file. */
|
|
155
|
+
collectionPath: string;
|
|
156
|
+
/** When true, passes `-r` to `bru run` to recurse into sub-folders. */
|
|
157
|
+
recursive?: boolean | undefined;
|
|
158
|
+
/** Environment name to pass to `--env`. */
|
|
159
|
+
env?: string | undefined;
|
|
160
|
+
/** Path where Bruno should write its JSON report (`--reporter-json`). */
|
|
161
|
+
reporterJsonPath?: string | undefined;
|
|
162
|
+
/** Extra CLI arguments appended verbatim after the collection path. */
|
|
163
|
+
extraArgs?: string[] | undefined;
|
|
164
|
+
/** Path to the `bru` binary. Defaults to `bru` (resolved from PATH). */
|
|
165
|
+
bruBin?: string | undefined;
|
|
166
|
+
/** Timeout in milliseconds for the entire run. `0` means no timeout. */
|
|
167
|
+
timeoutMs?: number | undefined;
|
|
168
|
+
}
|
|
169
|
+
interface ReportParserContract {
|
|
170
|
+
parse(reportPath: string): Promise<ExecutionSummary>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A request file discovered inside a Bruno collection directory.
|
|
175
|
+
*/
|
|
176
|
+
interface DiscoveredRequest {
|
|
177
|
+
/** The human-readable request name, taken from the `meta { name: ... }` block. */
|
|
178
|
+
requestName: string;
|
|
179
|
+
/**
|
|
180
|
+
* The path to the `.bru` file, relative to the collection root.
|
|
181
|
+
* Uses forward slashes on all platforms.
|
|
182
|
+
*/
|
|
183
|
+
requestFile: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Scans a Bruno collection directory for HTTP request files (`.bru` files
|
|
187
|
+
* whose `meta` block contains `type: http`).
|
|
188
|
+
*
|
|
189
|
+
* - The `environments/` directory is always excluded because it contains
|
|
190
|
+
* variable definition files, not request files.
|
|
191
|
+
* - Non-request `.bru` files (e.g. `type: collection`, `type: folder`)
|
|
192
|
+
* are silently skipped.
|
|
193
|
+
* - Any I/O error (missing directory, unreadable file, etc.) is caught
|
|
194
|
+
* internally and results in an empty list — scanning is non-fatal.
|
|
195
|
+
*/
|
|
196
|
+
declare class BruFileScanner {
|
|
197
|
+
/**
|
|
198
|
+
* Returns all HTTP request files found under `rootDir`.
|
|
199
|
+
*
|
|
200
|
+
* @param rootDir Absolute path to the collection root (the directory
|
|
201
|
+
* passed to `bru run`).
|
|
202
|
+
* @param recursive When `true`, descends into sub-directories. Mirrors
|
|
203
|
+
* the `-r` flag behaviour of `bru run`.
|
|
204
|
+
*/
|
|
205
|
+
scan(rootDir: string, recursive: boolean): Promise<DiscoveredRequest[]>;
|
|
206
|
+
private collectBruFiles;
|
|
207
|
+
private parseRequestFile;
|
|
208
|
+
private parseMetaBlock;
|
|
209
|
+
private nameFromPath;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
type EventHandler<T extends LifecycleEventName> = (payload: EventPayload<T>) => void;
|
|
213
|
+
/**
|
|
214
|
+
* Strongly-typed event bus wrapping a plain Map of handler sets.
|
|
215
|
+
* Does not extend Node's EventEmitter to remain platform-neutral.
|
|
216
|
+
*/
|
|
217
|
+
declare class TypedEventBus {
|
|
218
|
+
private readonly handlers;
|
|
219
|
+
on<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): () => void;
|
|
220
|
+
once<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): () => void;
|
|
221
|
+
off<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): void;
|
|
222
|
+
emit<T extends LifecycleEventName>(event: T, payload: EventPayload<T>): void;
|
|
223
|
+
removeAllListeners(event?: LifecycleEventName): void;
|
|
224
|
+
private getOrCreate;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
declare class BrunoLifecycleAdapter {
|
|
228
|
+
private readonly bus;
|
|
229
|
+
private readonly parser;
|
|
230
|
+
private readonly scanner;
|
|
231
|
+
constructor(parser?: ReportParserContract, scanner?: BruFileScanner);
|
|
232
|
+
on<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): () => void;
|
|
233
|
+
once<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): () => void;
|
|
234
|
+
off<T extends LifecycleEventName>(event: T, handler: EventHandler<T>): void;
|
|
235
|
+
run(config: AdapterRunConfig): Promise<ExecutionSummary>;
|
|
236
|
+
private emitReportEvents;
|
|
237
|
+
private buildArgs;
|
|
238
|
+
private buildEmptySummary;
|
|
239
|
+
private emit;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
declare class BrunoJsonReportParser implements ReportParserContract {
|
|
243
|
+
parse(reportPath: string): Promise<ExecutionSummary>;
|
|
244
|
+
private readRaw;
|
|
245
|
+
private mapToSummary;
|
|
246
|
+
private mapRequest;
|
|
247
|
+
private mapTest;
|
|
248
|
+
private mapAssertion;
|
|
249
|
+
private mapRequestStatus;
|
|
250
|
+
private mapTestStatus;
|
|
251
|
+
private makeError;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { type AdapterRunConfig, type AssertionResult, type AssertionResultEvent, BruFileScanner, BrunoJsonReportParser, BrunoLifecycleAdapter, type DiscoveredRequest, type ErrorDetail, type EventHandler, type EventMetadata, type EventPayload, type EventReliability, type ExecutionSummary, type LifecycleEvent, type LifecycleEventName, type ReportJsonReadyEvent, type ReportParserContract, type RequestDiscoveredEvent, type RequestFinishedEvent, type RequestResult, type RequestSkippedEvent, type RequestStartedEvent, type RequestStatus, type RunFailedEvent, type RunFinishedEvent, type RunStartedEvent, type RunStartingEvent, type RunStatus, type StderrEvent, type StdoutEvent, type TestFinishedEvent, type TestResult, type TestStartedEvent, type TestStatus, TypedEventBus };
|