@powerduck/openapi-cli 0.2.1 → 0.2.2

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 (2) hide show
  1. package/README.md +204 -560
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,266 +1,144 @@
1
1
  # @powerduck/openapi-cli
2
2
 
3
- > CI-ready command-line tool for batch-testing OpenAPI 3.2 documents across HTTP, SSE, WebSocket, GraphQL, gRPC, and MCP.
3
+ CI-ready command-line tool for batch-testing OpenAPI 3.2 documents across six protocols. Runs every operation in your spec, runs assertions against each response, and produces JSON, CLI, and HTML reports.
4
4
 
5
- Built on top of [@powerduck/openapi-request](https://www.npmjs.com/package/@powerduck/openapi-request). Point it at an OpenAPI JSON file or URL, get three report formats.
5
+ [![npm version](https://img.shields.io/npm/v/@powerduck/openapi-cli)](https://www.npmjs.com/package/@powerduck/openapi-cli)
6
+ [![license](https://img.shields.io/npm/l/@powerduck/openapi-cli)](https://github.com/PowerDuckie/openapi-cli/blob/main/LICENSE)
6
7
 
7
- ---
8
-
9
- ## Features
8
+ ## Links
10
9
 
11
- - **Multi-protocol out of the box** — HTTP, SSE (auto-detected), WebSocket, GraphQL, gRPC (all four streaming modes), and MCP (Streamable HTTP + stdio)
12
- - **Two assertion styles** — declarative `x-tests` arrays and full Postman `pm.test()` / `pm.expect()` scripts
13
- - **Three report formats** — machine-readable JSON, colored CLI output, and a self-contained interactive HTML report
14
- - **Local or remote specs** — load an OpenAPI document from a file path or an `http(s)://` URL
15
- - **CI-friendly** — non-zero exit on failure, configurable timeouts, bounded concurrency, `.env` support
16
- - **Flexible filtering** — run a subset by method, path, tag, or operationId
17
- - **Minimal runtime dependencies** — `@powerduck/openapi-request`, `commander`, and `@apidevtools/json-schema-ref-parser` for `$ref` resolution
10
+ - [Official Website](https://www.powerduck.com/opensource/openapi-cli.html)
11
+ - [Documentation](https://www.powerduck.com/docs/openapi-cli/introduction)
12
+ - [Live Demo](https://www.powerduck.com/demo/openapi-cli.html)
13
+ - [GitHub](https://github.com/PowerDuckie/openapi-cli)
14
+ - [npm](https://www.npmjs.com/package/@powerduck/openapi-cli)
18
15
 
19
16
  ---
20
17
 
21
- ## Installation
18
+ ## Quick Start
22
19
 
23
- ### Global
20
+ ### Install
24
21
 
25
22
  ```bash
26
23
  npm install -g @powerduck/openapi-cli
27
24
  ```
28
25
 
29
- ### Project-local (recommended for CI)
30
-
31
- ```bash
32
- npm install --save-dev @powerduck/openapi-cli
33
- ```
34
-
35
- ### npx (one-off)
26
+ ### Run tests from the CLI
36
27
 
37
28
  ```bash
38
- npx @powerduck/openapi-cli --spec openapi.json
29
+ openapi-cli --spec openapi.json --server https://api.example.com
39
30
  ```
40
31
 
41
- **Requirements:** Node.js >= 18.17.
32
+ ### Programmatic usage
42
33
 
43
- ---
44
-
45
- ## Quick Start
34
+ ```typescript
35
+ import { resolveConfig, runTests, generateJsonReport, generateHtmlReport } from "@powerduck/openapi-cli";
46
36
 
47
- ```bash
48
- # 1. Test a local OpenAPI document against its first server URL
49
- openapi-cli --spec openapi.json
37
+ const config = resolveConfig({
38
+ spec: "./openapi.json",
39
+ format: "json,html",
40
+ output: "./reports",
41
+ concurrency: 5,
42
+ });
50
43
 
51
- # 2. Override the server and write reports to a custom directory
52
- openapi-cli --spec openapi.json \
53
- --server https://api.staging.example.com \
54
- --output ./reports
44
+ const report = await runTests(config);
55
45
 
56
- # 3. Test a remote spec, only GET requests, 10 concurrent workers
57
- openapi-cli --spec https://docs.example.com/openapi.json \
58
- --method get \
59
- --concurrency 10
46
+ const jsonPath = generateJsonReport(report, config.outputDir);
47
+ const htmlPath = generateHtmlReport(report, config.outputDir);
60
48
 
61
- # 4. Use a config file for complex setups
62
- openapi-cli --config openapi-cli.config.json
49
+ console.log("Passed:", report.passed, "/", report.total);
63
50
  ```
64
51
 
65
- ---
52
+ ### GitHub Actions CI
66
53
 
67
- ## CLI Reference
54
+ ```yaml
55
+ name: API Tests
56
+ on: [push, pull_request]
68
57
 
58
+ jobs:
59
+ test:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - uses: actions/setup-node@v4
64
+ with:
65
+ node-version: 20
66
+ - run: npm ci
67
+ - name: Run API tests
68
+ run: npx @powerduck/openapi-cli --spec openapi.json --server ${{ secrets.API_URL }} --output ./reports
69
+ env:
70
+ BEARER_TOKEN: ${{ secrets.BEARER_TOKEN }}
71
+ - name: Upload reports
72
+ if: always()
73
+ uses: actions/upload-artifact@v4
74
+ with:
75
+ name: openapi-cli-reports
76
+ path: ./reports
69
77
  ```
70
- openapi-cli [options]
71
- ```
72
-
73
- ### Core
74
-
75
- | Option | Short | Default | Description |
76
- |--------|-------|---------|-------------|
77
- | `--spec <path-or-url>` | `-s` | **required** | Path or `http(s)://` URL to an OpenAPI 3.2 JSON document |
78
- | `--server <url>` | `-S` | *(from spec)* | Override the server base URL |
79
- | `--output <dir>` | `-o` | `./openapi-cli-report` | Directory for generated reports |
80
- | `--format <formats>` | `-f` | `json,cli,html` | Comma-separated report formats: `json`, `cli`, `html` |
81
- | `--config <path>` | `-c` | — | Path to a JSON config file (CLI args take precedence) |
82
-
83
- ### Execution
84
-
85
- | Option | Short | Default | Description |
86
- |--------|-------|---------|-------------|
87
- | `--concurrency <n>` | `-n` | `5` | Maximum number of concurrent requests |
88
- | `--timeout <ms>` | `-t` | `30000` | Per-request timeout in milliseconds (also applies to remote spec fetch) |
89
- | `--no-fail-on-error` | — | off | Exit with code 0 even when tests fail |
90
-
91
- ### Filtering
92
-
93
- | Option | Default | Description |
94
- |--------|---------|-------------|
95
- | `--method <methods>` | — | Comma-separated HTTP methods to include (e.g. `get,post`) |
96
- | `--path <patterns>` | — | Comma-separated regex patterns matched against the path |
97
- | `--tag <tags>` | — | Comma-separated OpenAPI tags to include |
98
- | `--operation-id <ids>` | — | Comma-separated operationIds to include |
99
-
100
- ### Network & TLS
101
-
102
- | Option | Short | Default | Description |
103
- |--------|-------|---------|-------------|
104
- | `--proxy <url>` | `-p` | — | HTTP(S) proxy URL (e.g. `http://proxy.corp:8080`) |
105
- | `--ca <path>` | — | — | Path to a CA certificate bundle (PEM) |
106
- | `--cert <path>` | — | — | Path to a client certificate (PEM) for mTLS |
107
- | `--key <path>` | — | — | Path to a client private key (PEM) for mTLS |
108
- | `--insecure` | `-k` | off | Skip TLS certificate verification |
109
-
110
- ### Authentication & Headers
111
-
112
- | Option | Short | Default | Description |
113
- |--------|-------|---------|-------------|
114
- | `--header <headers...>` | `-H` | — | Extra request headers, `Key: Value` format. Repeatable. |
115
- | `--bearer <token>` | — | — | Bearer token for the `Authorization` header |
116
- | `--variable <vars...>` | `-v` | — | Postman variables, `KEY=VALUE` format. Repeatable. |
117
- | `--env <path>` | — | — | Path to a `.env` file (does not override existing env vars) |
118
-
119
- ### gRPC
120
-
121
- | Option | Default | Description |
122
- |--------|---------|-------------|
123
- | `--grpc-no-reflection` | off | Disable gRPC server reflection; use proto files instead |
124
- | `--grpc-proto <paths...>` | — | Paths to `.proto` files or directories containing them |
125
-
126
- ### MCP
127
-
128
- | Option | Default | Description |
129
- |--------|---------|-------------|
130
- | `--mcp-transport <transport>` | `streamable-http` | MCP transport: `streamable-http` or `stdio` |
131
- | `--mcp-command <command>` | — | MCP stdio command (e.g. `npx`) |
132
- | `--mcp-args <args>` | — | MCP stdio arguments, space- or comma-separated |
133
- | `--mcp-cwd <dir>` | — | Working directory for the MCP stdio subprocess |
134
-
135
- ### Informational
136
-
137
- | Option | Short | Description |
138
- |--------|-------|-------------|
139
- | `--help` | `-h` | Show help |
140
- | `--version` | `-V` | Show version |
141
78
 
142
79
  ---
143
80
 
144
- ## Configuration File
145
-
146
- A JSON config file can be used instead of (or in addition to) CLI arguments. CLI arguments always take precedence.
147
-
148
- ```json
149
- {
150
- "specPath": "./openapi.json",
151
- "serverUrl": "https://api.staging.example.com",
152
- "outputDir": "./reports",
153
- "formats": ["json", "html"],
154
- "concurrency": 10,
155
- "timeout": 15000,
156
- "failOnError": true,
157
- "proxy": "http://proxy.corp:8080",
158
- "tls": {
159
- "strictSSL": true,
160
- "caCert": "./ca-bundle.pem",
161
- "clientCert": "./client.crt",
162
- "clientKey": "./client.key"
163
- },
164
- "headers": {
165
- "X-API-Key": "${API_KEY}",
166
- "X-Environment": "staging"
167
- },
168
- "auth": {
169
- "type": "bearer",
170
- "token": "${BEARER_TOKEN}"
171
- },
172
- "variables": {
173
- "host": "api.staging.example.com"
174
- },
175
- "filter": {
176
- "methods": ["get", "post"],
177
- "tags": ["v2", "public"],
178
- "paths": ["^/api/v2"],
179
- "operationIds": ["getUser", "createUser"]
180
- },
181
- "grpcReflection": true,
182
- "grpcProtoPaths": ["./proto"],
183
- "mcpTransport": "streamable-http",
184
- "mcpCommand": "npx",
185
- "mcpArgs": ["-y", "@modelcontextprotocol/server-everything"],
186
- "mcpCwd": "./mcp-servers"
187
- }
188
- ```
81
+ ## Features
189
82
 
190
- ### Config field reference
191
-
192
- | Field | Type | Description |
193
- |-------|------|-------------|
194
- | `specPath` | `string` | Local path or remote URL to the OpenAPI spec |
195
- | `serverUrl` | `string` | Override the server base URL |
196
- | `outputDir` | `string` | Output directory for reports |
197
- | `formats` | `string[]` | Report formats: `json`, `cli`, `html` |
198
- | `concurrency` | `number` | Max concurrent requests |
199
- | `timeout` | `number` | Per-request timeout in ms |
200
- | `failOnError` | `boolean` | Exit non-zero when tests fail |
201
- | `proxy` | `string` | HTTP(S) proxy URL |
202
- | `tls.strictSSL` | `boolean` | Verify TLS certificates (default `true`) |
203
- | `tls.caCert` | `string` | CA certificate bundle path |
204
- | `tls.clientCert` | `string` | Client certificate path (mTLS) |
205
- | `tls.clientKey` | `string` | Client private key path (mTLS) |
206
- | `headers` | `Record<string,string>` | Extra request headers |
207
- | `auth.type` | `string` | Auth type (currently `bearer`) |
208
- | `auth.token` | `string` | Bearer token |
209
- | `variables` | `Record<string,string>` | Postman variables |
210
- | `filter.methods` | `string[]` | HTTP method filter |
211
- | `filter.paths` | `string[]` | Path regex filter |
212
- | `filter.tags` | `string[]` | OpenAPI tag filter |
213
- | `filter.operationIds` | `string[]` | operationId filter |
214
- | `grpcReflection` | `boolean` | Use gRPC server reflection |
215
- | `grpcProtoPaths` | `string[]` | Proto file/directory paths |
216
- | `mcpTransport` | `string` | `streamable-http` or `stdio` |
217
- | `mcpCommand` | `string` | MCP stdio command |
218
- | `mcpArgs` | `string[]` | MCP stdio arguments |
219
- | `mcpCwd` | `string` | MCP stdio working directory |
83
+ - **CI-ready** — non-zero exit code on test failure, drops straight into GitHub Actions, GitLab CI, or any pipeline
84
+ - **Six protocols** — HTTP, SSE, WebSocket, GraphQL, gRPC, and MCP operations in one spec, one runner
85
+ - **Two assertion styles** — declarative `x-tests` arrays for simple checks, and full Postman `pm.test()` / `pm.expect()` scripts for complex ones
86
+ - **Three report formats** — machine-readable JSON, colored human-readable CLI output, and self-contained interactive HTML
87
+ - **Local or remote specs** — load from a file path or an `http(s)://` URL with redirect following and timeout enforcement
88
+ - **Flexible filtering** — run a subset of operations by method, path, tag, or operationId
89
+ - **Bounded concurrency** — configurable max concurrent requests with per-request timeout
90
+ - **Authentication** — Bearer token, Basic auth, API key (header/query), with environment variable expansion
91
+ - **Proxy support** — HTTP(S) proxy URL for corporate networks
92
+ - **TLS configuration** — CA cert, client cert/key, and insecure mode for self-signed certs
93
+ - **Built on `@powerduck/openapi-request`** — full OpenAPI parameter serialization (path/query/header/cookie styles and explode rules)
94
+ - **Dual ESM/CJS builds** — works with `import` and `require`
220
95
 
221
96
  ---
222
97
 
223
- ## Spec Sources
224
-
225
- ### Local file
98
+ ## CLI Usage
226
99
 
227
- ```bash
228
- openapi-cli --spec ./api/openapi.json
229
100
  ```
101
+ openapi-cli [options]
230
102
 
231
- ### Remote URL
232
-
233
- ```bash
234
- openapi-cli --spec https://docs.example.com/openapi.json
103
+ Options:
104
+ --spec <path> Path or URL to OpenAPI 3.2 spec (required)
105
+ --server <url> Override server base URL from spec
106
+ --output <dir> Output directory for reports (default: ./openapi-cli-report)
107
+ --format <formats> Comma-separated report formats: json,cli,html (default: json,cli,html)
108
+ --concurrency <n> Max concurrent requests (default: 5)
109
+ --timeout <ms> Per-request timeout in ms (default: 30000)
110
+ --filter-method <m> Filter by HTTP method (comma-separated)
111
+ --filter-path <regex> Filter by path regex
112
+ --filter-tag <tag> Filter by tag (comma-separated)
113
+ --filter-operation <id> Filter by operationId (comma-separated)
114
+ --bearer <token> Bearer token for Authorization header
115
+ --basic <user:pass> Basic auth credentials
116
+ --apikey <key=value> API key (name=value), use --apikey-in to set location
117
+ --apikey-in <location> API key location: header or query (default: header)
118
+ --header "Name: value" Extra header (repeatable)
119
+ --env-file <path> Path to .env file
120
+ --proxy <url> HTTP(S) proxy URL
121
+ --insecure Disable TLS certificate verification
122
+ --ca-cert <path> Path to CA certificate bundle (PEM)
123
+ --client-cert <path> Path to client certificate (PEM) for mTLS
124
+ --client-key <path> Path to client private key (PEM) for mTLS
125
+ --no-fail-on-error Exit 0 even if tests fail
126
+ --config <path> Path to JSON config file
127
+ --grpc-reflection Enable gRPC server reflection (default: true)
128
+ --grpc-proto <path> Path to .proto file or directory (repeatable)
129
+ --mcp-transport <mode> MCP transport: streamable-http or stdio (default: streamable-http)
130
+ --mcp-command <cmd> MCP stdio command to spawn
131
+ --mcp-args <args> MCP stdio command arguments (comma-separated)
132
+ --mcp-cwd <dir> MCP stdio working directory
133
+ -h, --help Show help
134
+ -v, --version Show version
235
135
  ```
236
136
 
237
- Remote specs are fetched with:
238
- - A configurable timeout (`--timeout`, default 30s)
239
- - Up to 5 automatic redirects
240
- - A descriptive error on HTTP failure (status code + message)
241
- - JSON validation after download
242
-
243
- The `User-Agent` header is set to `@powerduck/openapi-cli`.
244
-
245
137
  ---
246
138
 
247
- ## $ref Resolution
139
+ ## Declarative Assertions (x-tests)
248
140
 
249
- Internal `$ref` pointers (e.g. `#/components/schemas/User`) are automatically resolved before test execution. This means request body schemas, response schemas, and path items that reference `components` are fully inlined.
250
-
251
- - **Supported**: internal refs to any location in the same document (`#/...`)
252
- - **Not resolved**: external file or URL refs (`./other.json#/...`, `https://...`) — these are left as-is
253
- - **Circular refs**: safely ignored (no infinite recursion)
254
-
255
- Resolution is powered by [`@apidevtools/json-schema-ref-parser`](https://www.npmjs.com/package/@apidevtools/json-schema-ref-parser).
256
-
257
- ---
258
-
259
- ## Assertions
260
-
261
- ### Declarative assertions (`x-tests`)
262
-
263
- Add an `x-tests` array to any operation. Each assertion is an object with a `name` and an `assert` type.
141
+ Add an `x-tests` array to any operation. Each entry needs a `name` and an `assert` type:
264
142
 
265
143
  ```json
266
144
  {
@@ -275,404 +153,161 @@ Add an `x-tests` array to any operation. Each assertion is an object with a `nam
275
153
  { "name": "user is active", "assert": "jsonPath", "path": "$.status", "equals": "active" },
276
154
  { "name": "response under 2s", "assert": "responseTime", "max": 2000 },
277
155
  { "name": "body contains email", "assert": "bodyContains", "contains": "@" }
278
- ]
156
+ ],
157
+ "responses": { "200": { "description": "ok" } }
279
158
  }
280
159
  }
281
160
  }
282
161
  }
283
162
  ```
284
163
 
285
- #### Assertion types
164
+ ### Assertion Types
286
165
 
287
- | Type | Required fields | Optional fields | Description |
288
- |------|----------------|-----------------|-------------|
289
- | `status` | `value` (number) | — | HTTP status code must equal `value` |
290
- | `header` | `key` (string) | `contains` (string) | Header must exist; if `contains` is set, the header value must include the substring (case-insensitive) |
291
- | `bodyContains` | `contains` (string) | — | Response body must include the substring |
292
- | `bodyEquals` | `body` (string) | — | Response body must exactly equal `body` |
293
- | `jsonPath` | `path` (string) | `equals`, `exists` (boolean) | Resolve a JSONPath expression (dot notation + array indices). With `equals`, compare the resolved value. With `exists: true`, verify the path resolves. |
294
- | `responseTime` | `max` (number) | — | Response duration must be <= `max` milliseconds |
166
+ | `assert` | Required fields | Purpose |
167
+ |---|---|---|
168
+ | `status` | `value` | HTTP status equals `value` |
169
+ | `header` | `key`; optional `contains` | Header exists; optionally includes a substring |
170
+ | `bodyContains` | `contains` | Response body includes the substring |
171
+ | `bodyEquals` | `body` | Response body exactly equals `body` |
172
+ | `jsonPath` | `path`; optional `equals` / `exists` | Resolve a JSONPath and compare or check existence |
173
+ | `responseTime` | `max` | Duration must be `<= max` ms |
295
174
 
296
- #### JSONPath syntax
297
-
298
- Supports dot notation and array indices:
299
-
300
- - `$.data.id` → `body.data.id`
301
- - `$.users[0].name` → `body.users[0].name`
302
- - `data.items[2].value` → leading `$` is optional
175
+ ---
303
176
 
304
- ### Postman scripts (`x-postman-scripts`)
177
+ ## Postman Scripts
305
178
 
306
- Full Postman test scripts are executed by `@powerduck/openapi-request`'s built-in script sandbox. Use the standard `pm.test()` and `pm.expect()` API.
179
+ Embed a full Postman test script via `x-postman-scripts`:
307
180
 
308
181
  ```json
309
182
  {
310
183
  "paths": {
311
184
  "/users": {
312
185
  "get": {
186
+ "operationId": "listUsers",
313
187
  "x-postman-scripts": {
314
- "test": "pm.test('status is 200', () => pm.response.to.have.status(200)); pm.test('users is an array', () => pm.expect(pm.response.json().users).to.be.an('array')); pm.test('at least one user', () => pm.expect(pm.response.json().users.length).to.be.above(0));"
315
- }
188
+ "test": "pm.test('status is 200', () => pm.response.to.have.status(200)); pm.test('users is an array', () => pm.expect(pm.response.json().users).to.be.an('array'));"
189
+ },
190
+ "responses": { "200": { "description": "ok" } }
316
191
  }
317
192
  }
318
193
  }
319
194
  }
320
195
  ```
321
196
 
322
- Both `x-tests` and `x-postman-scripts` can coexist on the same operation. Declarative assertions run first, then Postman scripts.
323
-
324
- ### Implicit assertions (no user assertions defined)
325
-
326
- When an operation defines **no** `x-tests` and **no** `x-postman-scripts`, the CLI adds an implicit success check using each protocol's conventional success criterion. This prevents 404s, 500s, and gRPC errors from being silently marked as passed.
327
-
328
- | Protocol | Implicit success criterion | Failure example |
329
- |----------|---------------------------|-----------------|
330
- | HTTP | Status code is 2xx (200–299) | 404, 500, 301 |
331
- | SSE | Status code is 2xx (200–299) | 404, 500 |
332
- | GraphQL | Status 2xx **and** no top-level `errors` array | 200 with `{"errors": [...]}` |
333
- | gRPC | Status code is `0` (OK) | code 13 (INTERNAL), code 5 (NOT_FOUND) |
334
- | MCP | JSON-RPC response has `result` and no `error` | `{"error": {"code": -32601}}` |
335
- | WebSocket | Connection reached `open` state with no error | connection refused, handshake failure |
336
-
337
- If you need to test a non-success response (e.g., asserting that an endpoint returns 404), define an explicit `x-tests` assertion — implicit checks are skipped when any user assertion exists.
197
+ Declarative `x-tests` and `x-postman-scripts` may coexist on the same operation.
338
198
 
339
199
  ---
340
200
 
341
- ## Multi-Protocol Support
342
-
343
- ### HTTP / SSE
344
-
345
- Standard OpenAPI operations are executed as HTTP requests. SSE is auto-detected when:
346
- - The response content type is `text/event-stream`, or
347
- - The operation has `x-response-stream: true`
201
+ ## Programmatic API
348
202
 
349
- SSE responses are collected as a list of events.
203
+ ### `resolveConfig(args: CliArgs): CliConfig`
350
204
 
351
- ### GraphQL
205
+ Resolves CLI arguments into a normalized config. `CliArgs` uses string fields (`format`, `output`); `CliConfig` uses parsed fields (`formats` array, `outputDir` string).
352
206
 
353
- ```json
354
- {
355
- "paths": {
356
- "/graphql": {
357
- "post": {
358
- "x-protocol": "graphql",
359
- "x-graphql": {
360
- "endpoint": "https://api.example.com/graphql",
361
- "query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
362
- "operationName": "GetUser",
363
- "variablesSchema": {
364
- "type": "object",
365
- "properties": { "id": { "type": "string" } },
366
- "required": ["id"]
367
- }
368
- }
369
- }
370
- }
371
- }
372
- }
207
+ ```typescript
208
+ const config = resolveConfig({
209
+ spec: "./openapi.json",
210
+ format: "json,html",
211
+ output: "./reports",
212
+ concurrency: 10,
213
+ });
373
214
  ```
374
215
 
375
- ### gRPC
216
+ ### `runTests(config: CliConfig): Promise<TestReport>`
376
217
 
377
- All four gRPC method modes are supported: unary, server-streaming, client-streaming, and bidirectional-streaming.
378
-
379
- ```json
380
- {
381
- "paths": {
382
- "/grpc": {
383
- "post": {
384
- "x-protocol": "grpc",
385
- "x-grpc": {
386
- "address": "127.0.0.1:50051",
387
- "service": "demo.echo.Echo",
388
- "method": "Say",
389
- "kind": "unary",
390
- "reflection": true
391
- }
392
- }
393
- }
394
- }
395
- }
396
- ```
218
+ Runs all operations and returns a report.
397
219
 
398
- | Field | Description |
399
- |-------|-------------|
400
- | `address` | gRPC server `host:port` |
401
- | `service` | Fully-qualified service name |
402
- | `method` | Method name |
403
- | `kind` | `unary`, `server_streaming`, `client_streaming`, `bidi_streaming` |
404
- | `reflection` | Use server reflection (default `true`) |
405
- | `protoPaths` | Paths to `.proto` files when reflection is off |
406
- | `metadata` | Initial metadata (headers) |
407
- | `sample` | Sample request message (JSON) |
220
+ ### `generateJsonReport(report, outputDir): string`
408
221
 
409
- ### MCP
222
+ Writes `report.json` and returns the file path.
410
223
 
411
- ```json
412
- {
413
- "paths": {
414
- "/mcp": {
415
- "post": {
416
- "x-protocol": "mcp",
417
- "x-mcp": {
418
- "endpoint": "http://127.0.0.1:4200/mcp",
419
- "method": "tools/call",
420
- "name": "get_weather",
421
- "arguments": { "city": "San Francisco" }
422
- }
423
- }
424
- }
425
- }
426
- }
427
- ```
224
+ ### `generateHtmlReport(report, outputDir): string`
428
225
 
429
- Supports both Streamable HTTP and stdio transports (configured via `--mcp-transport`).
226
+ Writes a self-contained `report.html` and returns the file path.
430
227
 
431
- ### WebSocket
228
+ ---
432
229
 
433
- ```json
434
- {
435
- "paths": {
436
- "/ws": {
437
- "get": {
438
- "x-protocol": "websocket",
439
- "x-ws": {
440
- "url": "wss://api.example.com/ws",
441
- "subprotocols": ["chat"],
442
- "send": [{ "type": "subscribe", "channel": "updates" }]
443
- }
444
- }
445
- }
446
- }
447
- }
448
- ```
230
+ ## CliConfig Fields
231
+
232
+ | Field | Type | Default | Description |
233
+ |---|---|---|---|
234
+ | `specPath` | `string` | **required** | Path or URL to OpenAPI 3.2 JSON spec |
235
+ | `serverUrl` | `string` | *(from spec)* | Override server base URL |
236
+ | `outputDir` | `string` | `"./openapi-cli-report"` | Directory for report files |
237
+ | `formats` | `ReportFormat[]` | `["json","cli","html"]` | Report formats to generate |
238
+ | `filter` | `FilterConfig` | `undefined` | Filter which operations to test |
239
+ | `concurrency` | `number` | `5` | Max concurrent requests |
240
+ | `timeout` | `number` | `30000` | Per-request timeout in ms |
241
+ | `proxy` | `string` | `undefined` | HTTP(S) proxy URL |
242
+ | `tls` | `TlsConfig` | `undefined` | TLS / certificate configuration |
243
+ | `headers` | `Record<string, string>` | `undefined` | Extra headers sent with every request |
244
+ | `auth` | `AuthConfig` | `undefined` | Authentication configuration |
245
+ | `variables` | `Record<string, string>` | `undefined` | Postman `{{name}}` variables to inject |
246
+ | `failOnError` | `boolean` | `true` | Exit with non-zero code if any test fails |
247
+ | `envFile` | `string` | `undefined` | Path to a `.env` file |
248
+ | `grpcReflection` | `boolean` | `true` | gRPC: use server reflection |
249
+ | `grpcProtoPaths` | `string[]` | `undefined` | gRPC: paths to `.proto` files |
250
+ | `mcpTransport` | `"streamable-http" \| "stdio"` | `"streamable-http"` | MCP transport |
251
+ | `mcpCommand` | `string` | `undefined` | MCP stdio command |
252
+ | `mcpArgs` | `string[]` | `undefined` | MCP stdio arguments |
253
+ | `mcpCwd` | `string` | `undefined` | MCP stdio working directory |
449
254
 
450
255
  ---
451
256
 
452
- ## Report Formats
257
+ ## Config File
453
258
 
454
- ### JSON (`report.json`)
455
-
456
- A complete machine-readable report:
259
+ A JSON config file matching `Partial<CliConfig>`. Pass with `--config <path>`:
457
260
 
458
261
  ```json
459
262
  {
460
- "summary": {
461
- "total": 10,
462
- "passed": 8,
463
- "failed": 1,
464
- "errors": 1,
465
- "skipped": 0,
466
- "durationMs": 2340,
467
- "passRate": 80.0
263
+ "specPath": "./openapi.json",
264
+ "serverUrl": "https://api.staging.example.com",
265
+ "outputDir": "./reports",
266
+ "formats": ["json", "html"],
267
+ "concurrency": 10,
268
+ "timeout": 15000,
269
+ "failOnError": true,
270
+ "headers": {
271
+ "X-API-Key": "${API_KEY}",
272
+ "X-Environment": "staging"
468
273
  },
469
- "results": [
470
- {
471
- "operationId": "getUser",
472
- "path": "/users/{id}",
473
- "method": "GET",
474
- "protocol": "http",
475
- "status": "passed",
476
- "durationMs": 142,
477
- "response": { "status": 200, "contentType": "application/json", "body": { "id": 1 } },
478
- "assertions": [
479
- { "name": "status is 200", "passed": true }
480
- ],
481
- "timestamp": "2024-01-01T00:00:00.000Z"
482
- }
483
- ],
484
- "config": { "specPath": "./openapi.json" },
485
- "generatedAt": "2024-01-01T00:00:05.000Z",
486
- "version": "0.1.0"
274
+ "auth": {
275
+ "type": "bearer",
276
+ "token": "${BEARER_TOKEN}"
277
+ },
278
+ "filter": {
279
+ "methods": ["get", "post"],
280
+ "tags": ["v2", "public"]
281
+ }
487
282
  }
488
283
  ```
489
284
 
490
- ### CLI (stdout)
491
-
492
- Colored terminal output with a progress bar, per-test status, and assertion details. Failed assertions show error messages inline.
493
-
494
- ### HTML (`report.html`)
495
-
496
- A self-contained HTML file with:
497
- - Summary strip (total, passed, failed, errors, pass rate, duration)
498
- - Thin progress bar
499
- - Filter buttons (All / Passed / Failed / Errors)
500
- - Expandable test rows with assertion details and response body preview
501
- - Automatic light/dark mode based on system preference
502
- - No external dependencies — opens in any browser
285
+ Environment variable expansion (`${VAR_NAME}`) is applied to `serverUrl`, `proxy`, `headers` values, `auth.token`, and `variables` values.
503
286
 
504
287
  ---
505
288
 
506
- ## CI Integration
289
+ ## Supported Protocols
507
290
 
508
- ### GitHub Actions
291
+ | Protocol | Identifier | How it is selected |
292
+ |---|---|---|
293
+ | HTTP | `http` | Default for every standard operation |
294
+ | Server-Sent Events | `sse` | `text/event-stream` response content type, or `x-response-stream: true` |
295
+ | WebSocket | `websocket` | `x-protocol: "websocket"` |
296
+ | GraphQL | `graphql` | `x-protocol: "graphql"` |
297
+ | gRPC | `grpc` | `x-protocol: "grpc"` |
298
+ | MCP | `mcp` | `x-protocol: "mcp"` |
509
299
 
510
- ```yaml
511
- name: API Tests
512
- on: [push, pull_request]
513
-
514
- jobs:
515
- test:
516
- runs-on: ubuntu-latest
517
- steps:
518
- - uses: actions/checkout@v4
519
- - uses: actions/setup-node@v4
520
- with:
521
- node-version: 20
522
- - run: npm ci
523
- - name: Run API tests
524
- run: npx @powerduck/openapi-cli --spec openapi.json --server ${{ secrets.API_URL }} --output ./reports
525
- env:
526
- API_KEY: ${{ secrets.API_KEY }}
527
- - name: Upload reports
528
- if: always()
529
- uses: actions/upload-artifact@v4
530
- with:
531
- name: openapi-cli-reports
532
- path: ./reports
533
- ```
534
-
535
- ### GitLab CI
536
-
537
- ```yaml
538
- api_test:
539
- stage: test
540
- image: node:20
541
- script:
542
- - npm ci
543
- - npx @powerduck/openapi-cli --spec openapi.json --output ./reports
544
- artifacts:
545
- when: always
546
- paths:
547
- - ./reports/
548
- expire_in: 30 days
549
- ```
550
-
551
- ### Exit codes
552
-
553
- | Code | Meaning |
554
- |------|---------|
555
- | `0` | All tests passed (or `--no-fail-on-error` is set) |
556
- | `1` | One or more tests failed |
557
- | `2` | Configuration error, spec load failure, or runtime error |
300
+ Protocol can also be forced with the `x-protocol` extension on either the operation or the path item.
558
301
 
559
302
  ---
560
303
 
561
- ## Programmatic API
304
+ ## Reports
562
305
 
563
- ```ts
564
- import {
565
- runTests,
566
- resolveConfig,
567
- generateJsonReport,
568
- printCliReport,
569
- generateHtmlReport,
570
- loadSpec,
571
- isRemoteSpec,
572
- } from "@powerduck/openapi-cli";
573
-
574
- // Build a config from args
575
- const config = resolveConfig({
576
- spec: "./openapi.json",
577
- server: "https://api.staging.example.com",
578
- concurrency: 10,
579
- });
306
+ After a run, the tool writes (by default into `./openapi-cli-report`):
580
307
 
581
- // Run all tests
582
- const report = await runTests(config);
583
-
584
- // Generate reports
585
- printCliReport(report);
586
- generateJsonReport(report, "./reports");
587
- generateHtmlReport(report, "./reports");
588
-
589
- // Load a spec directly (local or remote)
590
- const spec = await loadSpec(config);
591
-
592
- // Check if a path is remote
593
- console.log(isRemoteSpec("https://example.com/spec.json")); // true
594
-
595
- // Exit with the right code
596
- if (report.summary.failed > 0 || report.summary.errors > 0) {
597
- process.exit(1);
598
- }
599
- ```
600
-
601
- ---
602
-
603
- ## Environment Variables
604
-
605
- ### `.env` files
606
-
607
- Use `--env .env` to load variables from a file into `process.env` before config resolution. Format is `KEY=VALUE` per line.
608
-
609
- ```env
610
- # Lines starting with # are ignored
611
- API_KEY=sk-live-abc123
612
- BEARER_TOKEN="eyJhbGciOi..."
613
- API_HOST=api.staging.example.com
614
- ```
615
-
616
- Behavior:
617
- - Existing `process.env` variables are **not** overwritten.
618
- - Surrounding single or double quotes are stripped from values.
619
- - Blank lines and comment lines (`#`) are ignored.
620
- - A missing `.env` file is silently ignored (no error).
621
-
622
- Loaded variables are available for `${VAR_NAME}` expansion in CLI config values (see below). To make a variable available as a Postman `{{variable}}` inside the OpenAPI spec, pass it with `--variable KEY=${KEY}`.
623
-
624
- ### Variable expansion
625
-
626
- `${VAR_NAME}` syntax is expanded in these config fields:
627
- - `serverUrl`
628
- - `proxy`
629
- - `headers` (values)
630
- - `auth.token`
631
- - `variables` (values)
632
-
633
- Example:
634
-
635
- ```bash
636
- openapi-cli --spec openapi.json \
637
- --server 'https://${API_HOST}/v1' \
638
- --header 'Authorization: Bearer ${BEARER_TOKEN}'
639
- ```
640
-
641
- ---
642
-
643
- ## Troubleshooting
644
-
645
- ### "OpenAPI spec not found"
646
-
647
- Verify the path exists. For remote specs, check the URL is reachable and returns HTTP 200.
648
-
649
- ### "Spec is not valid JSON"
650
-
651
- The spec must be a JSON object with a `paths` property. YAML specs are not supported — convert to JSON first.
652
-
653
- ### "Failed to fetch spec: HTTP 401"
654
-
655
- The remote spec URL requires authentication. Download it locally first or use a URL that is publicly accessible.
656
-
657
- ### gRPC "server reflection unavailable"
658
-
659
- Either enable reflection on the server, or use `--grpc-no-reflection` with `--grpc-proto` pointing to your `.proto` files.
660
-
661
- ### MCP stdio "command not found"
662
-
663
- Ensure `--mcp-command` is on your `PATH`, or use an absolute path. For `npx`-based servers, make sure the package is installed or use `npx -y <package>`.
664
-
665
- ### Tests are slow
666
-
667
- Reduce `--concurrency` if the target server rate-limits. Increase `--timeout` for slow endpoints. Use `--method` or `--tag` to test a subset.
668
-
669
- ### Tests fail with "(implicit)" in the assertion name
670
-
671
- This means the operation has no user-defined assertions and the implicit protocol check failed. For HTTP this means the status was not 2xx; for gRPC the code was not 0; for MCP the response contained a JSON-RPC error. Add an explicit `x-tests` assertion if the non-success response is expected (e.g. testing a 404 endpoint).
672
-
673
- ### All tests show "error" status
674
-
675
- Check the `error` field in `report.json` for details. Common causes: wrong server URL, network connectivity, missing auth headers, or TLS certificate issues (try `--insecure` for testing).
308
+ - `report.json` — the complete `TestReport` object as pretty-printed JSON
309
+ - `report.html` — a self-contained, dependency-free HTML report with a summary strip, progress bar, filter buttons, and expandable test rows
310
+ - stdout — a colored CLI summary with a progress bar and per-test assertions
676
311
 
677
312
  ---
678
313
 
@@ -692,11 +327,20 @@ npm run build
692
327
  npm test
693
328
 
694
329
  # Watch mode
695
- npm run test:watch
330
+ npx vitest watch
696
331
  ```
697
332
 
698
333
  ---
699
334
 
335
+ ## Related Packages
336
+
337
+ - [`@powerduck/openapi-request`](https://www.npmjs.com/package/@powerduck/openapi-request) — Execute OpenAPI operations with full parameter serialization
338
+ - [`@powerduck/openapi-parser`](https://www.npmjs.com/package/@powerduck/openapi-parser) — OpenAPI 3.2 parser, validator, and upgrader
339
+ - [`@powerduck/openapi-codegen`](https://www.npmjs.com/package/@powerduck/openapi-codegen) — Generate runnable request examples in 21 languages
340
+ - [`@powerduck/x-to-openapi`](https://www.npmjs.com/package/@powerduck/x-to-openapi) — Convert curl commands and Postman Collections to OpenAPI 3.2
341
+
342
+ ---
343
+
700
344
  ## License
701
345
 
702
346
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerduck/openapi-cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CI-ready CLI for batch-testing OpenAPI documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",