@voiden/runner 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +748 -0
  2. package/dist/blockSchemaRegistry.d.ts +49 -0
  3. package/dist/blockSchemaRegistry.d.ts.map +1 -0
  4. package/dist/blockSchemaRegistry.js +68 -0
  5. package/dist/blockSchemaRegistry.js.map +1 -0
  6. package/dist/cliElectron.d.ts +55 -0
  7. package/dist/cliElectron.d.ts.map +1 -0
  8. package/dist/cliElectron.js +120 -0
  9. package/dist/cliElectron.js.map +1 -0
  10. package/dist/headlessContext.d.ts +18 -0
  11. package/dist/headlessContext.d.ts.map +1 -0
  12. package/dist/headlessContext.js +85 -0
  13. package/dist/headlessContext.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +945 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/parser.d.ts +24 -0
  19. package/dist/parser.d.ts.map +1 -0
  20. package/dist/parser.js +87 -0
  21. package/dist/parser.js.map +1 -0
  22. package/dist/parserRegistry.d.ts +17 -0
  23. package/dist/parserRegistry.d.ts.map +1 -0
  24. package/dist/parserRegistry.js +27 -0
  25. package/dist/parserRegistry.js.map +1 -0
  26. package/dist/plugins/community.d.ts +42 -0
  27. package/dist/plugins/community.d.ts.map +1 -0
  28. package/dist/plugins/community.js +105 -0
  29. package/dist/plugins/community.js.map +1 -0
  30. package/dist/plugins/loader.d.ts +17 -0
  31. package/dist/plugins/loader.d.ts.map +1 -0
  32. package/dist/plugins/loader.js +120 -0
  33. package/dist/plugins/loader.js.map +1 -0
  34. package/dist/plugins/registry.d.ts +30 -0
  35. package/dist/plugins/registry.d.ts.map +1 -0
  36. package/dist/plugins/registry.js +52 -0
  37. package/dist/plugins/registry.js.map +1 -0
  38. package/dist/plugins/store.d.ts +19 -0
  39. package/dist/plugins/store.d.ts.map +1 -0
  40. package/dist/plugins/store.js +68 -0
  41. package/dist/plugins/store.js.map +1 -0
  42. package/dist/report/csv.d.ts +6 -0
  43. package/dist/report/csv.d.ts.map +1 -0
  44. package/dist/report/csv.js +71 -0
  45. package/dist/report/csv.js.map +1 -0
  46. package/dist/report/mail.d.ts +17 -0
  47. package/dist/report/mail.d.ts.map +1 -0
  48. package/dist/report/mail.js +84 -0
  49. package/dist/report/mail.js.map +1 -0
  50. package/dist/runner.d.ts +41 -0
  51. package/dist/runner.d.ts.map +1 -0
  52. package/dist/runner.js +193 -0
  53. package/dist/runner.js.map +1 -0
  54. package/dist/runtimeVars.d.ts +55 -0
  55. package/dist/runtimeVars.d.ts.map +1 -0
  56. package/dist/runtimeVars.js +248 -0
  57. package/dist/runtimeVars.js.map +1 -0
  58. package/dist/session.d.ts +10 -0
  59. package/dist/session.d.ts.map +1 -0
  60. package/dist/session.js +32 -0
  61. package/dist/session.js.map +1 -0
  62. package/dist/types.d.ts +45 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +3 -0
  65. package/dist/types.js.map +1 -0
  66. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,748 @@
1
+ # @voiden/runner
2
+
3
+ Headless CLI runner for [Voiden](https://voiden.app) — execute `.void` files
4
+ outside the app, in terminals, and CI/CD pipelines.
5
+
6
+ `.void` files are created and edited inside the **Voiden desktop app**.
7
+ This package runs them anywhere Node.js ≥ 18 is available: local terminals,
8
+ GitHub Actions, GitLab CI, Docker, and more.
9
+
10
+ ---
11
+
12
+ ## Table of contents
13
+
14
+ - [Installation](#installation)
15
+ - [Quick start](#quick-start)
16
+ - [Commands](#commands)
17
+ - [run](#run)
18
+ - [env](#env)
19
+ - [session](#session)
20
+ - [report](#report)
21
+ - [plugin](#plugin)
22
+ - [Environment variables](#environment-variables)
23
+ - [Runtime variables](#runtime-variables)
24
+ - [Sessions & Persistence](#sessions--persistence)
25
+ - [Plugins](#plugins)
26
+ - [voiden-scripting](#voiden-scripting)
27
+ - [simple-assertions](#simple-assertions)
28
+ - [voiden-faker](#voiden-faker)
29
+ - [voiden-advanced-auth](#voiden-advanced-auth)
30
+ - [voiden-graphql](#voiden-graphql)
31
+ - [Output formats](#output-formats)
32
+ - [Reports — CSV and email](#reports--csv-and-email)
33
+ - [Exit codes](#exit-codes)
34
+ - [CI/CD](#cicd)
35
+ - [Supported protocols](#supported-protocols)
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ npm install -g @voiden/runner
43
+ ```
44
+
45
+ Requires Node.js 18 or later.
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ ```bash
52
+ # Run a single file
53
+ voiden-runner run auth.void
54
+
55
+ # Run an entire folder recursively
56
+ voiden-runner run ./requests/
57
+
58
+ # With environment variable substitution
59
+ voiden-runner run ./requests/ --env .env.staging
60
+
61
+ # Stop on first failure (CI-friendly)
62
+ voiden-runner run ./tests/ --env .env.ci --stop-on-failure
63
+
64
+ # Export report to CSV + send by email
65
+ # SMTP settings (host, user, etc.) are read from your .env file
66
+ voiden-runner run ./tests/ \
67
+ --env .env.staging \
68
+ --csv ./results/report.csv \
69
+ --mail-to team@company.com
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Commands
75
+
76
+ ### `run`
77
+
78
+ ```
79
+ voiden-runner run <paths...> [options]
80
+ ```
81
+
82
+ `<paths...>` accepts any mix of files, directories (recursive), and glob patterns.
83
+
84
+ **Options**
85
+
86
+ | Flag | Description |
87
+ |---|---|
88
+ | `-e, --env <path>` | Standard `.env` file (`KEY=VALUE`) — merged on top of system env |
89
+ | `--env-var <k=v>` | Individual environment variable override (can be used multiple times) |
90
+ | `--bail` | Stop on first failure, exit 1 |
91
+ | `--stop-on-failure` | Alias for `--bail` (shell `set -e` friendly) |
92
+ | `--fail-on-error` | Run all files first, then exit 1 if any failed |
93
+ | `--show-req` | Print sent request headers and body for each request |
94
+ | `--show-res` | Print response headers and body for each request |
95
+ | `--verbose` | Print script logs, plugin messages, and section dividers |
96
+ | `--json` | Machine-readable JSON output (suppresses normal output) |
97
+ | `--no-session` | Completely stateless run (do not load/save results or runtime variables) |
98
+ | `--output-json <file>` | Write the full result object to a JSON file — pass the whole response data to the next CLI or script |
99
+ | `--csv <path>` | Export full report to a CSV file. Use `.` for the current directory (auto-generates filename) |
100
+ | `--mail` | Send HTML summary + attached CSV using `VOIDEN_MAIL_TO` (requires `--csv`) |
101
+ | `--mail-to <address>` | Send HTML summary + attached CSV to this address (requires `--csv`) |
102
+ | `--mail-from <address>` | Sender address (default: `VOIDEN_MAIL_FROM` env) |
103
+ | `--mail-subject <text>` | Email subject (default: `VOIDEN_MAIL_SUBJECT` env or auto-summary) |
104
+ | `--smtp-host <host>` | SMTP server host (default: `VOIDEN_SMTP_HOST` env) |
105
+ | `--smtp-port <port>` | SMTP server port (default: `VOIDEN_SMTP_PORT` env) |
106
+ | `--smtp-secure` | Use TLS for SMTP (default: `VOIDEN_SMTP_SECURE` env) |
107
+ | `--smtp-user <user>` | SMTP username (default: `VOIDEN_SMTP_USER` env) |
108
+ | `--smtp-pass <pass>` | SMTP password (default: `VOIDEN_SMTP_PASS` env) |
109
+
110
+ ### Environment Variables
111
+
112
+ ```
113
+ voiden-runner session status
114
+ voiden-runner session vars
115
+ voiden-runner session clear
116
+ ```
117
+
118
+ `status` shows counts of stored variables and results. `vars` lists all currently
119
+ stored runtime variables and their values. `clear` wipes all session
120
+ state (results and runtime variables).
121
+
122
+ ### `report`
123
+
124
+ ```
125
+ voiden-runner report generate [--csv <path>] [--mail] [--mail-to <address>]
126
+ voiden-runner report clear
127
+ ```
128
+
129
+ `generate` (alias `gen`) creates a combined report from all accumulated results
130
+ in the current session. `clear` wipes the results history only (runtime
131
+ variables are preserved).
132
+
133
+ Accepts all mail and SMTP options listed above.
134
+
135
+ ### `plugin`
136
+
137
+ ```
138
+ voiden-runner plugin install [names...] [--all]
139
+ voiden-runner plugin uninstall <name>
140
+ voiden-runner plugin enable [name] [--all]
141
+ voiden-runner plugin disable [name] [--all]
142
+ voiden-runner plugin list
143
+ ```
144
+
145
+ Plugin state is persisted to `~/.voiden/plugins.json` and survives across sessions.
146
+ Core plugins are **enabled by default** but can be disabled individually or all at once.
147
+ Community plugins must be installed before they can be enabled.
148
+
149
+ **`install` Options**
150
+
151
+ | Flag | Description |
152
+ |---|---|
153
+ | `--all` | Install all core plugins (makes them explicit in the store). Community plugins must be installed by name. |
154
+
155
+ **`enable` Options**
156
+
157
+ | Flag | Description |
158
+ |---|---|
159
+ | `--all` | Re-enable all disabled plugins (core and community). |
160
+
161
+ **`disable` Options**
162
+
163
+ | Flag | Description |
164
+ |---|---|
165
+ | `--all` | Disable all plugins (core and community). |
166
+
167
+ ---
168
+
169
+ ## Environment variables
170
+
171
+ Use `{{KEY}}` anywhere in a `.void` file — URL, headers, query params, body,
172
+ assertion expected values.
173
+
174
+ ### Sources (lowest → highest priority)
175
+
176
+ 1. **System environment** — `process.env`, including CI/CD platform variables
177
+ (GitHub Actions secrets, GitLab CI variables, etc.) — always available, no
178
+ flag needed
179
+ 2. **`--env` file** — standard `.env` file, overrides system variables
180
+ 3. **`--env-var` overrides** — per-run inline overrides, highest priority
181
+
182
+ ### `--env` file format
183
+
184
+ Standard `KEY=VALUE` format only — one variable per line:
185
+
186
+ ```env
187
+ # .env.staging
188
+ BASE_URL=https://staging.api.example.com
189
+ API_KEY=sk-staging-abc123
190
+ USER_ID=42
191
+ ```
192
+
193
+ ```bash
194
+ voiden-runner run ./requests/ --env .env.staging
195
+ ```
196
+
197
+ ### CI/CD — no `--env` file needed
198
+
199
+ CI/CD platform variables are injected into `process.env` automatically and are
200
+ available as `{{KEY}}` without any `--env` file:
201
+
202
+ ```yaml
203
+ # GitHub Actions
204
+ - run: voiden-runner run tests/
205
+ env:
206
+ BASE_URL: ${{ vars.BASE_URL }} # → {{BASE_URL}}
207
+ API_KEY: ${{ secrets.API_KEY }} # → {{API_KEY}}
208
+
209
+ # GitLab CI — CI_* variables available automatically
210
+ api-tests:
211
+ script: voiden-runner run tests/ # {{CI_COMMIT_SHA}}, {{API_KEY}} etc. just work
212
+ ```
213
+
214
+ Available inside scripts as `voiden.env.get('KEY')`.
215
+
216
+ ---
217
+
218
+ ## Runtime variables
219
+
220
+ Runtime variables let requests **chain** — a value extracted from one response
221
+ becomes available in the next request as `{{process.KEY}}`.
222
+
223
+ ### How it works
224
+
225
+ 1. Add a **runtime-variables block** to a `.void` file (use `/runtime-variables`
226
+ slash command in the Voiden app).
227
+ 2. Each row maps a **variable name** to a **capture expression** — a
228
+ `{{$res.xxx}}` or `{{$req.xxx}}` path into the request or response.
229
+ 3. After the request completes, the runner evaluates every enabled row and
230
+ stores the captured values **in memory** for the rest of the run.
231
+ 4. In any subsequent request (same file or later files), use `{{process.KEY}}`
232
+ to substitute the captured value.
233
+
234
+ ### Capture expression syntax
235
+
236
+ | Expression | Captures |
237
+ |---|---|
238
+ | `{{$res.body.access_token}}` | JSON field from response body |
239
+ | `{{$res.body.data.items[0].id}}` | Nested path with array index |
240
+ | `{{$res.headers.X-Request-Id}}` | Response header |
241
+ | `{{$res.status}}` | HTTP status code |
242
+ | `{{$res.statusText}}` | HTTP status text |
243
+ | `{{$res.time}}` | Response time in ms |
244
+ | `{{$req.headers.Authorization}}` | Header from the sent request |
245
+ | `{{$req.url}}` | Final URL (after variable substitution) |
246
+
247
+ ### Substitution syntax
248
+
249
+ Use `{{process.KEY}}` in URLs, headers, query params, body, and path params:
250
+
251
+ ```
252
+ GET {{process.baseUrl}}/users/{{process.userId}}
253
+ Authorization: Bearer {{process.token}}
254
+ ```
255
+
256
+ ### Script access
257
+
258
+ Inside pre-request and post-response scripts:
259
+
260
+ ```javascript
261
+ // Read a runtime variable
262
+ const token = voiden.variables.get('token')
263
+
264
+ // Write a runtime variable (available to all subsequent requests in this run)
265
+ voiden.variables.set('token', voiden.response.body.access_token)
266
+ ```
267
+
268
+ ### Persistence
269
+
270
+ By default, runtime variables are **persisted to disk** at `~/.voiden/.process.env.json`.
271
+ This allows you to share state across multiple `voiden-runner` commands.
272
+
273
+ - **To disable persistence** (keep variables in-memory only for a single run), use the `--no-session` flag.
274
+ - **To clear variables**, delete the `.process.env.json` file or use `voiden-runner session clear`.
275
+
276
+ The `.void` files themselves are never modified. This ensures that your source
277
+ files remain clean while still allowing for stateful execution chains.
278
+
279
+ ### Example — auth chain
280
+
281
+ **1. `login.void`** — POST /auth/login
282
+
283
+ ```
284
+ runtime-variables block:
285
+ token → {{$res.body.access_token}}
286
+ userId → {{$res.body.user.id}}
287
+ ```
288
+
289
+ **2. `get-profile.void`** — GET /users/{{process.userId}}
290
+
291
+ ```
292
+ Authorization: Bearer {{process.token}}
293
+ ```
294
+
295
+ Run them in order:
296
+
297
+ ```bash
298
+ voiden-runner run login.void get-profile.void --env .env
299
+ ```
300
+
301
+ The `token` and `userId` captured from `login.void` are automatically available
302
+ in `get-profile.void`.
303
+
304
+ ---
305
+
306
+ ## Sessions & Persistence
307
+
308
+ By default, `voiden-runner` operates in a **stateful session**. This means it
309
+ persists captured runtime variables and run results across multiple command
310
+ invocations until you explicitly clear them.
311
+
312
+ ### 1. Persistent State
313
+ Captured variables stay active until you clear the session. This is ideal for
314
+ multi-step workflows:
315
+
316
+ ```bash
317
+ voiden-runner run login.void # captures token
318
+ voiden-runner run get-profile.void # uses {{process.token}} automatically
319
+ ```
320
+
321
+ ### 2. Accumulated Results & Reporting
322
+
323
+ Every time you call `run`, the results are appended to a session results file.
324
+ This allows you to generate a single report for a series of separate runs.
325
+
326
+ ```bash
327
+ voiden-runner run login.void
328
+ voiden-runner run users.void
329
+ voiden-runner run posts.void
330
+
331
+ # Generate a combined CSV report for all 3 runs
332
+ voiden-runner report --csv ./session-report.csv
333
+
334
+ # Email the combined report
335
+ voiden-runner report --mail-to qa@company.com
336
+ ```
337
+
338
+ ### Managing the Session
339
+
340
+ Use the `session` command to check status or wipe all state.
341
+
342
+ ```bash
343
+ # See how many variables and results are stored
344
+ voiden-runner session status
345
+
346
+ # Wipe everything (results and runtime variables)
347
+ voiden-runner session clear
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Plugins
353
+
354
+ All core plugins are **enabled by default** — no `plugin install` step is needed.
355
+ They can be disabled individually (`plugin disable <name>`) or all at once (`plugin disable --all`).
356
+ The `plugin install` command is only required for community plugins.
357
+
358
+ ### `voiden-scripting`
359
+
360
+ Executes **pre-request** (`pre_script`) and **post-response** (`post_script`)
361
+ scripts embedded in the `.void` file.
362
+
363
+ **Languages supported in the runner:**
364
+
365
+ | Language | How it runs |
366
+ |---|---|
367
+ | JavaScript | In-process `AsyncFunction` — zero overhead |
368
+ | Python | `python3` subprocess (detected at startup; clear error if missing) |
369
+ | Shell (bash) | `bash` subprocess with temp file isolation |
370
+
371
+ **voiden API inside scripts**
372
+
373
+ | Property / Method | Description |
374
+ |---|---|
375
+ | `voiden.request.url` | Request URL (read/write in pre-script) |
376
+ | `voiden.request.method` | HTTP method (read/write in pre-script) |
377
+ | `voiden.request.headers` | Headers array `[{key, value}]` (read/write) |
378
+ | `voiden.request.body` | Request body string (read/write) |
379
+ | `voiden.request.queryParams` | Query params array (read/write) |
380
+ | `voiden.request.pathParams` | Path params array (read/write) |
381
+ | `voiden.response` | Response object (post-script only) |
382
+ | `voiden.response.status` | HTTP status code |
383
+ | `voiden.response.body` | Parsed response body |
384
+ | `voiden.response.headers` | Response headers `{key: value}` |
385
+ | `voiden.env.get('KEY')` | Read from `--env` file |
386
+ | `voiden.variables.get('KEY')` | Read a runtime variable |
387
+ | `voiden.variables.set('KEY', val)` | Write a runtime variable (available to next request) |
388
+ | `voiden.assert(actual, op, expected, msg?)` | Emit a pass/fail assertion |
389
+ | `voiden.log(level?, ...args)` | Emit a log line (`--verbose` to see them) |
390
+ | `voiden.cancel()` | Cancel the request from a pre-script |
391
+
392
+ **Assertion operators:** `==` `===` `!=` `!==` `>` `>=` `<` `<=`
393
+ `contains` `includes` `matches` (regex) `truthy` `falsy`
394
+ `eq` `neq` `gte` `lte` `greater` `less`
395
+
396
+ **Example — pre-script adds a timestamp header:**
397
+
398
+ ```javascript
399
+ voiden.request.headers.push({ key: 'X-Run-Ts', value: String(Date.now()), enabled: true })
400
+ voiden.log('info', 'Added X-Run-Ts')
401
+ ```
402
+
403
+ **Example — post-script asserts and captures a token:**
404
+
405
+ ```javascript
406
+ const body = voiden.response.body
407
+ voiden.assert(voiden.response.status, '==', 200, 'status is 200')
408
+ voiden.assert(body.access_token, 'truthy', null, 'token present')
409
+ voiden.variables.set('token', body.access_token)
410
+ ```
411
+
412
+ ---
413
+
414
+ ### `simple-assertions`
415
+
416
+ Evaluates assertion rows from an `assertions-table` block against the response.
417
+
418
+ **Field path syntax** (the `field` column):
419
+
420
+ | Path | Resolves to |
421
+ |---|---|
422
+ | `status` | HTTP status code |
423
+ | `statusText` | HTTP status text |
424
+ | `responseTime` | Response time in ms |
425
+ | `header.<Name>` | A response header value |
426
+ | `body.data.id` | JSON path into the parsed body |
427
+ | `body.items[0].name` | Array index access |
428
+
429
+ **Operators:** `equals` `notEquals` `contains` `notContains` `startsWith`
430
+ `endsWith` `greaterThan` `lessThan` `gte` `lte` `isEmpty` `isNotEmpty`
431
+ `isNull` `isNotNull` `matches` `exists` `notExists`
432
+
433
+ Assertion results appear under the request result line and in CSV/email reports.
434
+
435
+ ---
436
+
437
+ ### `voiden-faker`
438
+
439
+ Replaces `{{$faker.category.method(args)}}` patterns before the request is sent.
440
+
441
+ ```
442
+ {{$faker.person.firstName()}}
443
+ {{$faker.internet.email()}}
444
+ {{$faker.string.uuid()}}
445
+ {{$faker.number.int({"min":1,"max":100})}}
446
+ ```
447
+
448
+ ---
449
+
450
+ ### `voiden-advanced-auth`
451
+
452
+ Reads the `auth` block and injects authentication into the request.
453
+
454
+ **Auth types in the runner:** `bearer` `basic` `apiKey` (header or query)
455
+
456
+ OAuth 2.0, OAuth 1.0, AWS SigV4, Digest, NTLM — require the desktop app and
457
+ emit a warning when encountered in the runner.
458
+
459
+ `{{KEY}}` patterns in token/key/value fields are resolved from system env and the `--env` file.
460
+
461
+ ---
462
+
463
+ ### `voiden-graphql`
464
+
465
+ Rewrites `gqlquery` + `gqlvariables` blocks as a standard GraphQL-over-HTTP
466
+ POST (`Content-Type: application/json`, body `{query, variables}`).
467
+
468
+ ---
469
+
470
+ ## Output formats
471
+
472
+ ### Default (human-readable)
473
+
474
+ ```
475
+ voiden-runner · 3 files · 5 plugins active
476
+ ────────────────────────────────────────────────────────────────
477
+
478
+ [1/3] auth.void
479
+ ✓ REST POST https://api.example.com/auth 200 OK 342ms 1.2KB
480
+
481
+ [2/3] users.void
482
+ ✓ REST GET https://api.example.com/users 200 OK 128ms
483
+ assertions: 3 passed
484
+ ✓ status is 200
485
+ ✓ body has items
486
+ ✓ items count > 0
487
+
488
+ [3/3] delete-missing.void
489
+ ✗ REST DELETE https://api.example.com/users/999 404 Not Found 89ms
490
+ assertions: 1 passed · 1 failed
491
+ ✗ status is 200 (got 404, expected == 200)
492
+
493
+ ────────────────────────────────────────────────────────────────
494
+ Summary 3 requests · 2 passed · 1 failed · 559ms total
495
+ ────────────────────────────────────────────────────────────────
496
+ ```
497
+
498
+ ### `--json`
499
+
500
+ ```json
501
+ {
502
+ "summary": { "total": 3, "passed": 2, "failed": 1, "totalDurationMs": 559, "activePlugins": ["..."] },
503
+ "requests": [
504
+ {
505
+ "file": "/path/to/auth.void",
506
+ "protocol": "rest", "method": "POST", "url": "...",
507
+ "success": true, "status": 200, "durationMs": 342,
508
+ "requestHeaders": { "Content-Type": "application/json" },
509
+ "requestBody": "{\"email\":\"...\"}",
510
+ "responseHeaders": { "content-type": "application/json" },
511
+ "body": "{\"access_token\":\"...\"}",
512
+ "reportEntries": []
513
+ }
514
+ ]
515
+ }
516
+ ```
517
+
518
+ ---
519
+
520
+ ## Reports — CSV and email
521
+
522
+ ### CSV
523
+
524
+ ```bash
525
+ # Write to a specific file
526
+ voiden-runner run ./tests/ --csv ./results/report.csv
527
+
528
+ # Write to the current directory (auto-generates filename: voiden-report-<timestamp>.csv)
529
+ voiden-runner run ./tests/ --csv .
530
+ ```
531
+
532
+ CSV columns: `File`, `Protocol`, `Method`, `URL`, `Success`, `Status`,
533
+ `StatusText`, `DurationMs`, `SizeBytes`, `Error`, `RequestHeaders`,
534
+ `RequestBody`, `ResponseHeaders`, `ResponseBody`, `AssertionsPassed`,
535
+ `AssertionsFailed`, `AssertionDetail`
536
+
537
+ ### Email
538
+
539
+ ```bash
540
+ voiden-runner run ./tests/ \
541
+ --env .env.ci \
542
+ --mail-to qa@company.com
543
+ ```
544
+
545
+ Sends a dark-themed HTML report with per-request cards showing request/response
546
+ headers, bodies, and assertion results. Subject line is auto-generated from the
547
+ pass/fail summary unless `--mail-subject` is provided.
548
+
549
+ **SMTP Configuration**
550
+
551
+ The runner reads SMTP settings from your `.env` file (passed via `--env`) or
552
+ the system environment.
553
+
554
+ | Variable | Description |
555
+ |---|---|
556
+ | `VOIDEN_SMTP_HOST` | **Required** for email. SMTP server hostname (e.g., `smtp.gmail.com`). |
557
+ | `VOIDEN_SMTP_PORT` | SMTP port. Defaults to `587` (or `465` if secure). |
558
+ | `VOIDEN_SMTP_SECURE` | Set to `true` to use TLS/SSL (port 465). |
559
+ | `VOIDEN_SMTP_USER` | SMTP login username. |
560
+ | `VOIDEN_SMTP_PASS` | SMTP login password. |
561
+
562
+ ---
563
+
564
+ ## Exit codes
565
+
566
+ | Code | Condition |
567
+ |---|---|
568
+ | `0` | Run completed — unless `--fail-on-error` or `--bail`/`--stop-on-failure` is set |
569
+ | `1` | Any request failed and `--fail-on-error`, `--bail`, or `--stop-on-failure` is set |
570
+ | `1` | Usage error (bad flag, no files found, missing SMTP config, etc.) |
571
+
572
+ When exiting with code `1` due to failures, a final message is printed:
573
+
574
+ ```
575
+ ✗ Run failed — 3 requests failed. Exiting with code 1.
576
+ (use this exit code in your shell script to abort on failure)
577
+ ```
578
+
579
+ This exit code works universally — bash (`$?`), PowerShell (`$LASTEXITCODE`),
580
+ `set -e`, `&&`/`||` chains, GitHub Actions, GitLab CI, Jenkins, CircleCI, and
581
+ any other CI/CD system.
582
+
583
+ ## Passing results to other CLI commands
584
+
585
+ ### `--output-json <file>`
586
+
587
+ Writes the full result — the whole response object and array — to a JSON file.
588
+ Normal terminal output still shows. The next CLI, script, or tool reads the file
589
+ and gets everything: status, headers, body, assertions, duration.
590
+
591
+ ```bash
592
+ # Write results to a file, then pass the whole object to the next tool
593
+ voiden-runner run auth.void --output-json result.json
594
+ my-deploy-cli --data result.json
595
+
596
+ # Chain multiple runs — each appends its own file
597
+ voiden-runner run login.void --output-json login.json
598
+ voiden-runner run users.void --output-json users.json
599
+ my-report-tool login.json users.json
600
+ ```
601
+
602
+ The JSON structure written to the file is the same as `--json` stdout output:
603
+
604
+ ```json
605
+ {
606
+ "summary": { "total": 1, "passed": 1, "failed": 0, "totalDurationMs": 342 },
607
+ "requests": [
608
+ {
609
+ "file": "/path/to/auth.void",
610
+ "protocol": "rest",
611
+ "method": "POST",
612
+ "url": "https://api.example.com/auth",
613
+ "success": true,
614
+ "status": 200,
615
+ "durationMs": 342,
616
+ "body": "{\"access_token\":\"sk-abc\",\"user\":{\"id\":42}}",
617
+ "requestHeaders": { "Content-Type": "application/json" },
618
+ "responseHeaders": { "content-type": "application/json" }
619
+ }
620
+ ]
621
+ }
622
+ ```
623
+
624
+ ### `--json` stdout pipe
625
+
626
+ Use `--json` to pipe the same structure directly to another command:
627
+
628
+ ```bash
629
+ voiden-runner run auth.void --json | jq .
630
+ voiden-runner run tests/ --json | my-cli --stdin
631
+ voiden-runner run tests/ --json > results.json && python3 analyse.py results.json
632
+ ```
633
+
634
+ ### Runtime variable chaining
635
+
636
+ Variables captured via `{{$res.body.xxx}}` blocks persist to
637
+ `~/.voiden/.process.env.json` between separate `voiden-runner run` calls —
638
+ no piping or files needed:
639
+
640
+ ```bash
641
+ voiden-runner run login.void # captures token via runtime-variables block
642
+ voiden-runner run users.void # uses {{process.token}} automatically
643
+ ```
644
+
645
+ ---
646
+
647
+ ## CI/CD
648
+
649
+ Works on every CI/CD platform that supports Node.js — GitHub Actions, GitLab CI,
650
+ CircleCI, Jenkins, Azure Pipelines, Bitbucket Pipelines, and more. Install once,
651
+ run anywhere:
652
+
653
+ ```bash
654
+ npm install -g @voiden/runner # bash / macOS / Linux
655
+ npm install -g @voiden/runner # PowerShell / Windows cmd — identical
656
+ ```
657
+
658
+ **Windows (cmd.exe / PowerShell)**
659
+
660
+ ```batch
661
+ :: cmd.exe
662
+ voiden-runner run tests\ --env .env.ci --stop-on-failure
663
+ if %ERRORLEVEL% neq 0 exit /b 1
664
+ ```
665
+
666
+ ```powershell
667
+ # PowerShell
668
+ voiden-runner run tests/ --env .env.ci --stop-on-failure
669
+ if ($LASTEXITCODE -ne 0) { exit 1 }
670
+ ```
671
+
672
+ ### GitHub Actions
673
+
674
+ ```yaml
675
+ jobs:
676
+ api-tests:
677
+ runs-on: ubuntu-latest
678
+ steps:
679
+ - uses: actions/checkout@v4
680
+ - uses: actions/setup-node@v4
681
+ with: { node-version: '20' }
682
+
683
+ - run: npm install -g @voiden/runner
684
+
685
+ - name: Write env
686
+ run: |
687
+ echo "BASE_URL=${{ secrets.BASE_URL }}" >> .env.ci
688
+ echo "API_KEY=${{ secrets.API_KEY }}" >> .env.ci
689
+
690
+ - name: Run tests
691
+ run: |
692
+ voiden-runner run ./tests/ \
693
+ --env .env.ci \
694
+ --stop-on-failure \
695
+ --json | tee results.json
696
+
697
+ - uses: actions/upload-artifact@v4
698
+ if: always()
699
+ with: { name: api-test-results, path: results.json }
700
+ ```
701
+
702
+ ### GitLab CI
703
+
704
+ ```yaml
705
+ api-tests:
706
+ image: node:20
707
+ script:
708
+ - npm install -g @voiden/runner
709
+ - echo "BASE_URL=$BASE_URL" >> .env.ci
710
+ - echo "API_KEY=$API_KEY" >> .env.ci
711
+ - voiden-runner run ./tests/ --env .env.ci --stop-on-failure
712
+ ```
713
+
714
+ ### With scripting enabled
715
+
716
+ If your `.void` files use `voiden-scripting` blocks and you trust the content:
717
+
718
+ ```bash
719
+ # JavaScript only (no Python/Shell risk)
720
+ voiden-runner run ./tests/ --env .env.ci --stop-on-failure
721
+
722
+ # With Python — ensure python3 is available in the runner image
723
+ # python3 --version → Python 3.x.x
724
+ voiden-runner run ./tests/ --env .env.ci --stop-on-failure
725
+ ```
726
+
727
+ ### Request chaining in CI
728
+
729
+ Variables captured via runtime-variable blocks are shared across all files in a
730
+ single `voiden-runner run` invocation:
731
+
732
+ ```bash
733
+ # login.void captures {{token}}, get-users.void uses {{process.token}}
734
+ voiden-runner run login.void get-users.void create-post.void \
735
+ --env .env.ci \
736
+ --stop-on-failure
737
+ ```
738
+
739
+ ---
740
+
741
+ ## Supported protocols
742
+
743
+ | Protocol | Block types |
744
+ |---|---|
745
+ | REST (HTTP/HTTPS) | `method`, `url`, `headers-table`, `query-table`, `json_body`, … |
746
+ | WebSocket (`ws://` / `wss://`) | `socket-request`, `surl`, `smethod` |
747
+ | gRPC (`grpc://` / `grpcs://`) | `socket-request`, `proto`, `grpc-messages-node` |
748
+ | GraphQL | `gqlquery`, `gqlvariables` |