api-tracer-kit 1.0.0 → 1.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/CHANGELOG.md +17 -0
- package/README.md +77 -342
- package/cli/bin/api-tracer.mjs +93 -2
- package/cli/setup.mjs +182 -0
- package/cli/test.mjs +97 -0
- package/cli/web/app.css +1 -2
- package/dist/axios.cjs +8 -0
- package/dist/axios.cjs.map +1 -1
- package/dist/axios.js +8 -0
- package/dist/axios.js.map +1 -1
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +8 -0
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +8 -0
- package/dist/react.js.map +1 -1
- package/dist/ui.cjs +8 -0
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.js +8 -0
- package/dist/ui.js.map +1 -1
- package/docs/architecture.md +136 -0
- package/docs/configuration.md +275 -0
- package/docs/console.md +428 -0
- package/docs/frameworks.md +192 -0
- package/docs/getting-started.md +120 -0
- package/docs/security.md +143 -0
- package/docs/tracer.md +330 -0
- package/docs/troubleshooting.md +194 -0
- package/package.json +2 -1
package/docs/console.md
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
# The console
|
|
2
|
+
|
|
3
|
+
A separate process that maps every endpoint in your source, receives your app's
|
|
4
|
+
live traffic, and lets you fire any endpoint and read the response. It needs no
|
|
5
|
+
framework, no build step and — in most projects — no configuration.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx api-tracer start
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Reads your source, writes a catalog, and opens at `http://127.0.0.1:4400`.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `api-tracer scan` | read the source, write the endpoint catalog |
|
|
18
|
+
| `api-tracer serve` | run the console |
|
|
19
|
+
| `api-tracer start` | scan, then serve |
|
|
20
|
+
| `api-tracer report` | build the insight report |
|
|
21
|
+
| `api-tracer init` | write a config file from what the scan guessed |
|
|
22
|
+
|
|
23
|
+
### Options
|
|
24
|
+
|
|
25
|
+
| | |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `--root <dir>` | the project to read (default: cwd) |
|
|
28
|
+
| `--data <dir>` | where captures are kept (default: `<root>/.api-tracer`) |
|
|
29
|
+
| `--port <n>` | default `4400` |
|
|
30
|
+
| `--host <addr>` | default `127.0.0.1` |
|
|
31
|
+
| `--preset <name>` | force a scanner preset |
|
|
32
|
+
| `--format md\|json` | report format (default `md`) |
|
|
33
|
+
| `--out <file>` | write the report to a file (default: stdout) |
|
|
34
|
+
| `--env <name>` | which base URL the report describes |
|
|
35
|
+
| `--force` | let `init` overwrite an existing config |
|
|
36
|
+
|
|
37
|
+
## The catalog
|
|
38
|
+
|
|
39
|
+
`scan` reads your source into `<data>/endpoints.json`: one entry per endpoint,
|
|
40
|
+
with its module, HTTP verb, path template, the `${...}` holes in it, whether it
|
|
41
|
+
sends query params or a body, its auth quirk, and the file and line it came
|
|
42
|
+
from.
|
|
43
|
+
|
|
44
|
+
It is a plain JSON file, so a deployed console needs no source and no build —
|
|
45
|
+
just the file that shipped with it. Re-running `scan` where there is no source
|
|
46
|
+
keeps the catalog that is already there.
|
|
47
|
+
|
|
48
|
+
The scan also counts how many files outside its own module reference each
|
|
49
|
+
endpoint. Anything at zero is flagged **unused in app** and gets its own filter.
|
|
50
|
+
|
|
51
|
+
> This is a substring count, so `import * as services` or a re-export would hide
|
|
52
|
+
> a real usage. Confirm with a grep before deleting anything.
|
|
53
|
+
|
|
54
|
+
See [Configuration](./configuration.md) for how endpoints are found and what to
|
|
55
|
+
do when the presets do not fit your codebase.
|
|
56
|
+
|
|
57
|
+
## Live recording
|
|
58
|
+
|
|
59
|
+
The main way to use it. Run your app normally with `reportTo` pointed at the
|
|
60
|
+
console, and every call it makes streams in as you click — no export, no import,
|
|
61
|
+
no typing payloads.
|
|
62
|
+
|
|
63
|
+
Each request lands on its endpoint in the tree with the real query params, the
|
|
64
|
+
real payload and the real path ids already filled in. The response is scored
|
|
65
|
+
too, so an endpoint your app just used successfully goes green without you
|
|
66
|
+
running anything.
|
|
67
|
+
|
|
68
|
+
Click **Live** in the header to pause or resume. The counter shows what has
|
|
69
|
+
arrived.
|
|
70
|
+
|
|
71
|
+
A call that no endpoint in the catalog explains is **not** dropped — it is
|
|
72
|
+
adopted as an endpoint of its own, marked *not found in source*, and behaves
|
|
73
|
+
like the rest. Recording only what could be matched would quietly hide real
|
|
74
|
+
traffic. **Clear uncatalogued** removes them.
|
|
75
|
+
|
|
76
|
+
## Reading pass and fail
|
|
77
|
+
|
|
78
|
+
Some APIs return HTTP 200 for failures and put the real verdict in the envelope:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{ "status": 801, "success": false, "message": "Authentication token header missing" }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
So an endpoint counts as **passing** only when the HTTP status is 2xx **and**
|
|
85
|
+
the body's status code is not at or above the failure threshold **and** the
|
|
86
|
+
success flag is not `false`. The response viewer shows the HTTP code and the
|
|
87
|
+
body code side by side.
|
|
88
|
+
|
|
89
|
+
Configure the convention with `envelope` in the config file, or set it to
|
|
90
|
+
`false` if your API uses the status line honestly.
|
|
91
|
+
|
|
92
|
+
## Using one endpoint
|
|
93
|
+
|
|
94
|
+
Pick it in the tree. The request pane is tabbed like Postman:
|
|
95
|
+
|
|
96
|
+
- **Params** — query params, as JSON, stringified into one param, or as a raw
|
|
97
|
+
query string
|
|
98
|
+
- **Headers** — extra headers, merged on top of the token and `Content-Type` the
|
|
99
|
+
server adds
|
|
100
|
+
- **Body** — the type selector: `none` / `JSON` / `multipart/form-data` /
|
|
101
|
+
`x-www-form-urlencoded`. JSON gives a validated editor; the form types give
|
|
102
|
+
key/value rows. Switching type keeps your values.
|
|
103
|
+
- **Capture** — rules that store response values as `{{variables}}`
|
|
104
|
+
|
|
105
|
+
The body type is preselected from what your app actually sent, so a captured
|
|
106
|
+
`FormData` call opens on **multipart/form-data** with its fields already in the
|
|
107
|
+
table, and **Send** replays it that way. Replaying a form post as JSON would
|
|
108
|
+
fail for reasons that have nothing to do with the endpoint.
|
|
109
|
+
|
|
110
|
+
Also on the pane: the source location (`src/services/appointments.js:91`), the
|
|
111
|
+
resolved URL updating live as you type, a field per path placeholder, and after
|
|
112
|
+
**Send** (or Cmd/Ctrl+Enter) the status, body code, latency, size, response body
|
|
113
|
+
and headers. **Copy as cURL** leaves the token as a placeholder.
|
|
114
|
+
|
|
115
|
+
JSON editors are syntax highlighted with line numbers and Tab indents rather
|
|
116
|
+
than leaving the field — a coloured layer behind a transparent textarea, so
|
|
117
|
+
there is no editor library and no dependency.
|
|
118
|
+
|
|
119
|
+
### Query params: three modes
|
|
120
|
+
|
|
121
|
+
Switching carries what you typed across.
|
|
122
|
+
|
|
123
|
+
- **JSON** — `{"page": 1, "ids": [3, 4], "filter": {"status": "new"}}`, serialized
|
|
124
|
+
the way axios does by default: `?page=1&ids[]=3&ids[]=4&filter[status]=new`
|
|
125
|
+
- **stringified** — the whole object in one encoded param,
|
|
126
|
+
`?params=%7B%22page%22%3A1%7D`. The param name is editable.
|
|
127
|
+
- **query string** — pasted straight from DevTools and sent byte-for-byte, so an
|
|
128
|
+
already-encoded `%2B` stays `%2B` instead of becoming `%252B`
|
|
129
|
+
|
|
130
|
+
The URL bar shows the fully resolved URL in every mode, built by the same rules
|
|
131
|
+
the server uses, so the preview never disagrees with what goes out.
|
|
132
|
+
|
|
133
|
+
## Bulk runs and replay
|
|
134
|
+
|
|
135
|
+
**Run every GET endpoint** and **Run all GETs in this module** check many at
|
|
136
|
+
once, six in parallel.
|
|
137
|
+
|
|
138
|
+
Two deliberate limits on bulk GET runs:
|
|
139
|
+
|
|
140
|
+
- **Only GETs are ever run in bulk.** A bulk run that fired nineteen DELETEs
|
|
141
|
+
would be a very bad afternoon.
|
|
142
|
+
- **Path placeholders are left blank**, so `/appointments/${data.id}.json` is
|
|
143
|
+
called as `/appointments/.json`. That proves the endpoint answers; it does not
|
|
144
|
+
prove it behaves correctly with a real id. Treat bulk results as a smoke test.
|
|
145
|
+
|
|
146
|
+
For each placeholder a run takes the first of: the value captured from real
|
|
147
|
+
traffic, a variable sharing the placeholder's name, then blank. The run reports
|
|
148
|
+
how many endpoints went out blank, so a weak result never looks like a strong
|
|
149
|
+
one, and every saved result records the URL that was actually called.
|
|
150
|
+
|
|
151
|
+
### Replay every captured call
|
|
152
|
+
|
|
153
|
+
Re-sends every endpoint that has a sample — GET and writes alike — with the
|
|
154
|
+
payload, query params, path ids and body type your app used.
|
|
155
|
+
|
|
156
|
+
It shows exactly what it will do before it does anything: a count per method,
|
|
157
|
+
the environment and base URL, and what it is leaving out. Defaults, all
|
|
158
|
+
changeable in that dialog:
|
|
159
|
+
|
|
160
|
+
- **DELETEs excluded.** Opt in with a checkbox; the endpoints are named.
|
|
161
|
+
- **Logout excluded**, since it can invalidate the token the rest depends on.
|
|
162
|
+
- **Endpoints needing a signed token excluded.**
|
|
163
|
+
- **Payloads containing a stripped secret are skipped**, because sending the
|
|
164
|
+
literal `<redacted>` would fail for a reason unrelated to the endpoint.
|
|
165
|
+
- **Runs one at a time**, in order. Captured writes often depend on each other,
|
|
166
|
+
and a failure order you cannot reproduce is worse than a slow run.
|
|
167
|
+
- **Refused entirely on a `prod` environment.**
|
|
168
|
+
|
|
169
|
+
This is not a dry run. Every POST creates a new record each time.
|
|
170
|
+
|
|
171
|
+
While it runs the dialog becomes a progress view: a pass/fail bar, a running
|
|
172
|
+
count, the call in flight, and the last few results. **Stop** halts after the
|
|
173
|
+
call in progress. Results survive navigating away and reloading, because the run
|
|
174
|
+
is held on the server.
|
|
175
|
+
|
|
176
|
+
A call that fails during a replay keeps its response — open that endpoint and
|
|
177
|
+
the response pane shows what came back, labelled *from the last replay*, until
|
|
178
|
+
you send it again yourself. Those endpoints are marked **failed when replayed**.
|
|
179
|
+
|
|
180
|
+
Results are saved and survive a restart, so you can see what changed since
|
|
181
|
+
yesterday. An endpoint that was passing and now fails is marked **broke since
|
|
182
|
+
last run**. Switching environment clears them, since dev results would be
|
|
183
|
+
misleading on stage.
|
|
184
|
+
|
|
185
|
+
## Contract drift
|
|
186
|
+
|
|
187
|
+
A status check cannot see the failure that matters most on an API that answers
|
|
188
|
+
`200` for everything: a backend that quietly drops a field, or changes an id
|
|
189
|
+
from a number to a string, still looks green.
|
|
190
|
+
|
|
191
|
+
So every passing response is reduced to its **shape** — field paths and types,
|
|
192
|
+
values ignored — and compared with the endpoint's baseline. The first good
|
|
193
|
+
response becomes the baseline; after that a change is reported:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
-data[].pinned, +data[].author, data[].id: number->string
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Drifted endpoints get an amber dot in the tree, a panel on the endpoint, and
|
|
200
|
+
their own filter. When the change is intended, **This change is expected, make
|
|
201
|
+
it the baseline** promotes the new shape and clears the flag.
|
|
202
|
+
|
|
203
|
+
Live traffic feeds this for free: your app's own calls set and check the
|
|
204
|
+
baseline as you use it.
|
|
205
|
+
|
|
206
|
+
## Coverage
|
|
207
|
+
|
|
208
|
+
The home screen shows how much of the API your app has actually exercised — how
|
|
209
|
+
many endpoints have ever been seen, which modules remain untouched, and how many
|
|
210
|
+
have drifted. Module rows show `seen/total`.
|
|
211
|
+
|
|
212
|
+
An endpoint nobody has triggered has not been proven either way, which is
|
|
213
|
+
different from failing. The **never captured** filter is the to-do list.
|
|
214
|
+
|
|
215
|
+
## Variables and chaining
|
|
216
|
+
|
|
217
|
+
Use `{{name}}` in any path value, query param or payload. Set them by hand in
|
|
218
|
+
**Variables**, or capture them from a response: a rule like
|
|
219
|
+
`patient_id = data.0.id` stores that value after a passing call, for every later
|
|
220
|
+
call. Dotted paths walk arrays. A path that finds nothing reports an error
|
|
221
|
+
rather than silently storing a blank.
|
|
222
|
+
|
|
223
|
+
Variables are not a place for tokens — those stay in the console's own token
|
|
224
|
+
store.
|
|
225
|
+
|
|
226
|
+
## Import
|
|
227
|
+
|
|
228
|
+
For traffic captured earlier, or from someone else's session.
|
|
229
|
+
|
|
230
|
+
1. Open your app in Chrome, DevTools → **Network**, tick **Preserve log**.
|
|
231
|
+
2. Click through the screens you care about.
|
|
232
|
+
3. Right-click the request list → **Save all as HAR with content**.
|
|
233
|
+
4. Drop the file anywhere on the console page.
|
|
234
|
+
|
|
235
|
+
Each request is matched back to the endpoint the scanner found, by method and
|
|
236
|
+
path shape, so a real call to `POST /appointments/4821/accept` lands on the
|
|
237
|
+
template `/appointments/${data.id}/accept`. From it the console keeps the path
|
|
238
|
+
id, the query params and the request body, which become the endpoint's starting
|
|
239
|
+
values.
|
|
240
|
+
|
|
241
|
+
For a single request, **Copy as cURL** in DevTools and paste it into the import
|
|
242
|
+
dialog.
|
|
243
|
+
|
|
244
|
+
The import reports what it could not place — usually an endpoint the backend has
|
|
245
|
+
but the frontend does not call yet.
|
|
246
|
+
|
|
247
|
+
## Export
|
|
248
|
+
|
|
249
|
+
Every module page has **Export HAR** and **Export cURL**; the home screen has
|
|
250
|
+
**Export all captured**.
|
|
251
|
+
|
|
252
|
+
| | |
|
|
253
|
+
| --- | --- |
|
|
254
|
+
| **HAR** | a valid HAR 1.2 file with request, query string, payload and recorded response body. Opens in Chrome DevTools, imports into Postman, and re-imports here. |
|
|
255
|
+
| **cURL** | one runnable command per endpoint, labelled with its source location |
|
|
256
|
+
| **Postman** | a Collection v2.1. Modules become folders; each request carries the real query params and body in the right mode, so a captured form post imports as form-data, not broken JSON. Host and token are left as `{{baseUrl}}` and `{{token}}`. |
|
|
257
|
+
|
|
258
|
+
One entry per endpoint, not per call — the most recent capture wins, which keeps
|
|
259
|
+
an export of a busy session readable.
|
|
260
|
+
|
|
261
|
+
All exports carry `<paste your token>` in place of a real token, so an export is
|
|
262
|
+
safe to attach to a ticket.
|
|
263
|
+
|
|
264
|
+
## The report
|
|
265
|
+
|
|
266
|
+
**Report** in the header builds an insight report from everything the console
|
|
267
|
+
already holds. No extra capture, no new run.
|
|
268
|
+
|
|
269
|
+
| Section | |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| **Coverage** | exercised vs total, per module, and which modules nobody has walked |
|
|
272
|
+
| **Failing** | status, body code and the message pulled out of the envelope, with regressions and replay failures marked |
|
|
273
|
+
| **Contract drift** | response shapes that changed, with the field-level diff |
|
|
274
|
+
| **Risks** | writes never exercised, captured DELETEs, 2xx responses carrying an error code, payloads whose secrets were stripped |
|
|
275
|
+
| **Hygiene** | endpoints referenced nowhere, duplicate routes, calls not found in the source |
|
|
276
|
+
| **Auth surface** | which endpoints need which token, and which third-party ones deliberately get none |
|
|
277
|
+
| **Latency** | median, p95 and the slowest endpoints |
|
|
278
|
+
| **Inventory** | request params, body fields and response fields per captured endpoint, from real traffic rather than hand-written docs |
|
|
279
|
+
| **Trend** | pass rate and coverage over time, one line appended per run |
|
|
280
|
+
|
|
281
|
+
**Export Markdown** for a ticket, **Export JSON** for CI. Or from the command
|
|
282
|
+
line:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npx api-tracer report --format json --out api-report.json
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The latency numbers are one measurement per endpoint, from its most recent call
|
|
289
|
+
— indicative, not a benchmark.
|
|
290
|
+
|
|
291
|
+
## Signing in
|
|
292
|
+
|
|
293
|
+
Click **Set token** and paste one. The token is held by the server, never sent
|
|
294
|
+
back to the page, and written owner-only (`chmod 600`) to the data directory so
|
|
295
|
+
a restart does not sign you out. **Sign out** deletes it.
|
|
296
|
+
|
|
297
|
+
If your API has a multi-step sign-in worth automating — password, then a
|
|
298
|
+
one-time code, then an organisation — describe it in the config file and the
|
|
299
|
+
console will drive it, so nobody has to paste anything. See
|
|
300
|
+
[sign-in flows](./configuration.md#sign-in-flows).
|
|
301
|
+
|
|
302
|
+
Third-party endpoints (anything flagged absolute) never receive your token.
|
|
303
|
+
|
|
304
|
+
## Why calls go through the server
|
|
305
|
+
|
|
306
|
+
The page posts the composed request to the console and the server forwards it.
|
|
307
|
+
Calling the API from the page directly would be blocked by CORS, and the token
|
|
308
|
+
would have to sit in client-side JavaScript.
|
|
309
|
+
|
|
310
|
+
## Getting around
|
|
311
|
+
|
|
312
|
+
- **The logo** goes back to the overview from anywhere
|
|
313
|
+
- **Breadcrumbs** at the top of every page, each part clickable
|
|
314
|
+
- **Escape** backs out one level — endpoint, then module, then overview. Ignored
|
|
315
|
+
while you are typing or a dialog is open.
|
|
316
|
+
- Filters: method, and pass/fail/not-run. Search matches function name, path and
|
|
317
|
+
module.
|
|
318
|
+
|
|
319
|
+
Typed path values and payloads are kept in the browser's local storage, so a
|
|
320
|
+
reload does not lose your work.
|
|
321
|
+
|
|
322
|
+
## Environment variables
|
|
323
|
+
|
|
324
|
+
| | |
|
|
325
|
+
| --- | --- |
|
|
326
|
+
| `API_TRACER_DATA` | where captures are kept (default `<project>/.api-tracer`) |
|
|
327
|
+
| `PORT` / `HOST` | default `4400` / `127.0.0.1` |
|
|
328
|
+
| `BASE_PATH` | serve under a path, e.g. `/api-console`, behind a proxy |
|
|
329
|
+
| `ALLOWED_ORIGINS` | comma-separated origins allowed to post recordings; localhost always works |
|
|
330
|
+
| `CONSOLE_USER` / `CONSOLE_PASS` | basic auth on everything except `/api/record` |
|
|
331
|
+
| `LOCK_RECORDING=1` | recording cannot be paused from the UI |
|
|
332
|
+
| `LOCK_ENV=<name>` | pins the environment |
|
|
333
|
+
|
|
334
|
+
`HOST` stays on `127.0.0.1` by default, so a laptop instance is not exposed by
|
|
335
|
+
accident.
|
|
336
|
+
|
|
337
|
+
## Running one for a team
|
|
338
|
+
|
|
339
|
+
Every developer whose app has the tracer posts to the console whether or not
|
|
340
|
+
they ever open it. The captures accumulate: one person exercises appointments,
|
|
341
|
+
another exercises rota, and the console ends up holding both.
|
|
342
|
+
|
|
343
|
+
Two variables stop one person's click changing things for everyone:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
LOCK_RECORDING=1 LOCK_ENV=dev npx api-tracer serve
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
- Without `LOCK_RECORDING`, one developer clicking **Live** silently stops
|
|
350
|
+
recording for the whole team.
|
|
351
|
+
- Without `LOCK_ENV`, someone switching to stage redirects everyone's next
|
|
352
|
+
replay and clears the shared results.
|
|
353
|
+
|
|
354
|
+
The console also shows **whose** session the shared token belongs to, since
|
|
355
|
+
everyone replays as that user.
|
|
356
|
+
|
|
357
|
+
`CONSOLE_USER` / `CONSOLE_PASS` turn on basic auth. **`/api/record` stays
|
|
358
|
+
open**, because the app posts recordings from a browser that cannot send those
|
|
359
|
+
credentials. The gate protects reading captures, replaying calls and the stored
|
|
360
|
+
token; it does not stop someone who can reach the host from posting junk
|
|
361
|
+
recordings. If that matters, put the whole thing behind VPN or SSO.
|
|
362
|
+
|
|
363
|
+
It is single-user by design: one token, one environment, one set of results. Two
|
|
364
|
+
people on a shared instance will overwrite each other's environment and results.
|
|
365
|
+
|
|
366
|
+
### Behind a proxy
|
|
367
|
+
|
|
368
|
+
The mount path is forwarded as-is, so tell the console where it lives:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
BASE_PATH=/api-console HOST=0.0.0.0 ALLOWED_ORIGINS=https://app.example.com npx api-tracer serve
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Then point the tracer at the same path:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
apiTracer.init({ reportTo: '/api-console' });
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Same origin means no CORS and no mixed content.
|
|
381
|
+
|
|
382
|
+
> **Mixed content.** An HTTPS app cannot post to an `http://` console; the
|
|
383
|
+
> browser blocks it silently. Terminate TLS in front of the console, or serve it
|
|
384
|
+
> under a path on the app's own origin.
|
|
385
|
+
|
|
386
|
+
## HTTP API
|
|
387
|
+
|
|
388
|
+
Everything the UI does is available directly.
|
|
389
|
+
|
|
390
|
+
| Route | |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| `GET /api/endpoints` | catalog, base URLs, auth info, results, samples, live state |
|
|
393
|
+
| `POST /api/token` | store or clear a token |
|
|
394
|
+
| `GET/POST /api/login` | the configured sign-in flow: the next step, and submitting it |
|
|
395
|
+
| `POST /api/env` | switch environment |
|
|
396
|
+
| `POST /api/call` | send one endpoint |
|
|
397
|
+
| `POST /api/run` | bulk run; GETs only unless explicitly allowed |
|
|
398
|
+
| `POST /api/run/preview` | what a run would do |
|
|
399
|
+
| `GET /api/run/status`, `POST /api/run/cancel` | progress and cancellation |
|
|
400
|
+
| `POST /api/record` | one live call from the running app |
|
|
401
|
+
| `GET/POST/DELETE /api/live` | recorder state: counts, pause, reset |
|
|
402
|
+
| `GET/POST/DELETE /api/contracts` | response-shape baselines; POST accepts a drift |
|
|
403
|
+
| `GET /api/export` | `?module=&format=har\|curl\|postman` |
|
|
404
|
+
| `POST /api/import` | import a HAR or a cURL command |
|
|
405
|
+
| `DELETE /api/samples` | drop all captured samples |
|
|
406
|
+
| `DELETE /api/uncatalogued` | drop adopted endpoints |
|
|
407
|
+
| `GET/POST /api/variables` | read or set `{{variables}}` |
|
|
408
|
+
| `DELETE /api/results` | clear saved results |
|
|
409
|
+
| `GET /api/report`, `GET /api/report/export` | the report model, and `?format=md\|json` |
|
|
410
|
+
|
|
411
|
+
## What lives in the data directory
|
|
412
|
+
|
|
413
|
+
`<project>/.api-tracer/`, or wherever `--data` points.
|
|
414
|
+
|
|
415
|
+
| | |
|
|
416
|
+
| --- | --- |
|
|
417
|
+
| `endpoints.json` | the catalog, written by `scan` |
|
|
418
|
+
| `samples.json` | requests captured from real traffic |
|
|
419
|
+
| `results.json` | the last result per endpoint |
|
|
420
|
+
| `contracts.json` | response-shape baselines |
|
|
421
|
+
| `uncatalogued.json` | endpoints adopted from traffic |
|
|
422
|
+
| `variables.json` | `{{variables}}` |
|
|
423
|
+
| `lastrun.json`, `runs.jsonl` | the last run, and one line per run for the trend |
|
|
424
|
+
| `tokens.json` | the session token, written `chmod 600` |
|
|
425
|
+
|
|
426
|
+
**Add `.api-tracer/` to your `.gitignore`.** It contains real request and
|
|
427
|
+
response bodies from the environment you are watching, and a real token. See
|
|
428
|
+
[Security](./security.md).
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Frameworks
|
|
2
|
+
|
|
3
|
+
The tracer only needs one thing: `init()` called once, at the entry point,
|
|
4
|
+
before anything makes a request. No bundler, Babel, TypeScript or ESLint
|
|
5
|
+
configuration is required anywhere.
|
|
6
|
+
|
|
7
|
+
## Plain JavaScript
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script type="module">
|
|
11
|
+
import { apiTracer } from 'https://esm.sh/api-tracer-kit';
|
|
12
|
+
apiTracer.init();
|
|
13
|
+
</script>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
or in a bundled project:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import { apiTracer } from 'api-tracer-kit';
|
|
20
|
+
apiTracer.init();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## React
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// src/main.tsx — before ReactDOM.render / createRoot
|
|
27
|
+
import { apiTracer } from 'api-tracer-kit';
|
|
28
|
+
|
|
29
|
+
apiTracer.init({ reportTo: import.meta.env.DEV });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or from inside the tree, which is StrictMode-safe:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useApiTracer } from 'api-tracer-kit/react';
|
|
36
|
+
|
|
37
|
+
function App() {
|
|
38
|
+
useApiTracer({ reportTo: true });
|
|
39
|
+
return <Routes />;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`useApiTracer` deliberately does **not** destroy on unmount: in StrictMode that
|
|
44
|
+
would tear the interceptors down immediately after installing them.
|
|
45
|
+
|
|
46
|
+
To render the traces:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useApiTraces } from 'api-tracer-kit/react';
|
|
50
|
+
|
|
51
|
+
function CallCount() {
|
|
52
|
+
const { traces, clear } = useApiTraces();
|
|
53
|
+
return <button onClick={clear}>{traces.length} calls</button>;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Updates are batched on an animation frame, so a burst of calls on page load
|
|
58
|
+
re-renders once rather than once per request.
|
|
59
|
+
|
|
60
|
+
## Create React App
|
|
61
|
+
|
|
62
|
+
CRA's `ModuleScopePlugin` allows imports only from `src/` and `node_modules/`.
|
|
63
|
+
Always import by **package name** — never by a relative path to a checkout
|
|
64
|
+
outside the project:
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { apiTracer } from 'api-tracer-kit'; // ✅
|
|
68
|
+
import { apiTracer } from '../../../packages/api-tracer-kit'; // ❌ rejected
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A `yarn add file:` dependency creates a **symlink**, and webpack resolves
|
|
72
|
+
symlinks to their real path — which lands outside the project and is rejected
|
|
73
|
+
the same way. Depend on a published version, or on a packed tarball:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm pack --pack-destination ./vendor # in the package
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
"api-tracer-kit": "file:vendor/api-tracer-kit-1.0.1.tgz"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Gate it on `REACT_APP_ENV`, not `NODE_ENV` — a dev or stage deploy is a
|
|
84
|
+
production build, so `NODE_ENV` would compile the tracer out of exactly the
|
|
85
|
+
environments it is meant for:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
// src/services/apiRecorder.js
|
|
89
|
+
import axios from 'axios';
|
|
90
|
+
import { apiTracer } from 'api-tracer-kit';
|
|
91
|
+
|
|
92
|
+
const isEnabled =
|
|
93
|
+
process.env.REACT_APP_ENV === 'dev' || process.env.REACT_APP_ENV === 'stage';
|
|
94
|
+
|
|
95
|
+
if (isEnabled) {
|
|
96
|
+
apiTracer.init({ reportTo: process.env.REACT_APP_API_CONSOLE_URL || 'http://localhost:4400' });
|
|
97
|
+
apiTracer.useAxios(axios);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
// src/index.js — one import, at the top
|
|
103
|
+
import './services/apiRecorder';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Compare `process.env.REACT_APP_ENV` **directly**, not through a variable, so
|
|
107
|
+
DefinePlugin substitutes the literal and the whole condition folds away in a
|
|
108
|
+
production build.
|
|
109
|
+
|
|
110
|
+
## Vite
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// src/main.ts
|
|
114
|
+
import { apiTracer } from 'api-tracer-kit';
|
|
115
|
+
|
|
116
|
+
if (import.meta.env.DEV) {
|
|
117
|
+
apiTracer.init({ reportTo: true });
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`import.meta.env.DEV` is statically replaced, so the branch is removed from a
|
|
122
|
+
production build.
|
|
123
|
+
|
|
124
|
+
## Next.js
|
|
125
|
+
|
|
126
|
+
Importing the package on the server is safe — it evaluates no browser global —
|
|
127
|
+
but there is no point tracing a render, so guard the call:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// app/providers.tsx
|
|
131
|
+
'use client';
|
|
132
|
+
|
|
133
|
+
import { useApiTracer } from 'api-tracer-kit/react';
|
|
134
|
+
|
|
135
|
+
export function Providers({ children }) {
|
|
136
|
+
useApiTracer({ reportTo: process.env.NODE_ENV === 'development' });
|
|
137
|
+
return children;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For the Pages Router, `pages/_app.tsx` works the same way.
|
|
142
|
+
|
|
143
|
+
To trace server-side calls too, `init()` in `instrumentation.ts` — Node has
|
|
144
|
+
`fetch`, so it works there as well:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
export async function register() {
|
|
148
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
149
|
+
const { apiTracer } = await import('api-tracer-kit');
|
|
150
|
+
apiTracer.init({ include: ['/api/'] });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Server traces stay in that process's memory; they do not reach the browser
|
|
156
|
+
panel.
|
|
157
|
+
|
|
158
|
+
## Node.js
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
import { apiTracer } from 'api-tracer-kit';
|
|
162
|
+
|
|
163
|
+
apiTracer.init();
|
|
164
|
+
await fetch('https://api.example.com/users');
|
|
165
|
+
console.log(apiTracer.getTraces());
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Node 18+ has global `fetch`, so it is traced. There is no `XMLHttpRequest`, and
|
|
169
|
+
that transport is skipped rather than erroring. For axios on Node's `http`
|
|
170
|
+
adapter, `useAxios()` is required — no global patch can reach it.
|
|
171
|
+
|
|
172
|
+
## Testing
|
|
173
|
+
|
|
174
|
+
Use an isolated instance rather than the shared one, so cases cannot leak into
|
|
175
|
+
each other:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { ApiTracer } from 'api-tracer-kit';
|
|
179
|
+
|
|
180
|
+
let tracer;
|
|
181
|
+
beforeEach(() => { tracer = new ApiTracer().init({ transports: ['fetch'] }); });
|
|
182
|
+
afterEach(() => { tracer.destroy(); });
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`destroy()` restores the original `fetch`, so a suite that traces is
|
|
186
|
+
indistinguishable from one that does not.
|
|
187
|
+
|
|
188
|
+
## Webpack, Rollup, esbuild, Parcel
|
|
189
|
+
|
|
190
|
+
Nothing to configure. The package ships ESM and CJS with `sideEffects: false`,
|
|
191
|
+
so an unused import is dropped entirely and the four entry points are bundled
|
|
192
|
+
separately — importing the tracer never pulls in React.
|