@umeshindu222/apisnap 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -73
- package/dist/core/runner.js +336 -145
- package/dist/core/runner.js.map +1 -1
- package/dist/middleware/index.d.ts +8 -2
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +44 -35
- package/dist/middleware/index.js.map +1 -1
- package/package.json +42 -42
package/README.md
CHANGED
|
@@ -7,182 +7,319 @@
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Why APISnap?
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
**The Solution:** APISnap plugs directly into your Express app, **automatically discovers every route you've registered**, and then health-checks all of them in seconds with zero configuration.
|
|
12
|
+
Every time you change your Express backend, manually testing 20+ endpoints in Postman is slow and error-prone. APISnap **auto-discovers every route** and health-checks all of them in seconds — with zero config.
|
|
15
13
|
|
|
16
14
|
---
|
|
17
15
|
|
|
18
|
-
##
|
|
16
|
+
## Features
|
|
19
17
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
18
|
+
- 🔍 **Auto Route Discovery** — Scans your full Express router stack including sub-routers
|
|
19
|
+
- 🔐 **Full Auth Support** — JWT, API Keys, Cookies, multiple headers simultaneously
|
|
20
|
+
- 🔁 **Retry Logic** — Auto-retry failed requests with exponential backoff
|
|
21
|
+
- ⚡ **Slow Route Detection** — Flags endpoints exceeding your threshold
|
|
22
|
+
- 📊 **HTML Reports** — Beautiful visual reports for sharing/archiving
|
|
23
|
+
- 💾 **JSON Export** — Structured output for CI/CD pipelines
|
|
24
|
+
- ⚙️ **Config File** — Persist options in `.apisnaprc.json`
|
|
25
|
+
- 🎯 **Method Filter** — Test only GET, POST, etc.
|
|
26
|
+
- 🧠 **Smart Params** — Auto-replaces `:id`, `:slug`, `:uuid` with safe defaults
|
|
27
|
+
- 🚨 **Auth Hints** — Tells you exactly how to fix 401/403 errors
|
|
28
|
+
- 🏗️ **CI/CD Ready** — Exit code 1 on failures for pipeline integration
|
|
26
29
|
|
|
27
30
|
---
|
|
28
31
|
|
|
29
|
-
##
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Step 1 — Install & add middleware
|
|
30
35
|
|
|
31
|
-
### 1. Install & Add Middleware
|
|
32
36
|
```bash
|
|
33
37
|
npm install @umeshindu222/apisnap
|
|
34
38
|
```
|
|
35
39
|
|
|
36
|
-
In your Express application, initialize APISnap **after** all your routes:
|
|
37
40
|
```javascript
|
|
38
41
|
const express = require('express');
|
|
39
42
|
const apisnap = require('@umeshindu222/apisnap');
|
|
43
|
+
|
|
40
44
|
const app = express();
|
|
41
45
|
|
|
46
|
+
// ✅ Your routes go here
|
|
42
47
|
app.get('/users', (req, res) => res.json({ users: [] }));
|
|
48
|
+
app.post('/users', (req, res) => res.json({ message: 'Created' }));
|
|
43
49
|
|
|
44
|
-
//
|
|
50
|
+
// ✅ APISnap goes AFTER your routes (so it can discover them)
|
|
51
|
+
// ✅ APISnap goes BEFORE global auth middleware (to allow discovery)
|
|
45
52
|
apisnap.init(app);
|
|
46
53
|
|
|
54
|
+
// ⚠️ If you use global auth middleware, place it AFTER apisnap.init():
|
|
55
|
+
// app.use(authMiddleware); ← AFTER init, not before
|
|
56
|
+
|
|
47
57
|
app.listen(3000);
|
|
48
58
|
```
|
|
49
59
|
|
|
50
|
-
### 2
|
|
51
|
-
|
|
60
|
+
### Step 2 — Run
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx @umeshindu222/apisnap --port 3000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🔐 Fixing 401 / 403 Errors
|
|
69
|
+
|
|
70
|
+
This is the most common issue. APISnap sends real HTTP requests, so **protected routes require credentials** just like any client would.
|
|
71
|
+
|
|
72
|
+
### JWT / Bearer Token
|
|
73
|
+
```bash
|
|
74
|
+
npx @umeshindu222/apisnap -H "Authorization: Bearer eyJhbGci..."
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### API Key header
|
|
52
78
|
```bash
|
|
53
|
-
npx @umeshindu222/apisnap
|
|
79
|
+
npx @umeshindu222/apisnap -H "x-api-key: my-secret-key"
|
|
54
80
|
```
|
|
55
81
|
|
|
56
|
-
###
|
|
82
|
+
### Cookie / Session auth
|
|
83
|
+
```bash
|
|
84
|
+
npx @umeshindu222/apisnap --cookie "sessionId=abc123; connect.sid=xyz"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Multiple headers at once (`-H` can be repeated)
|
|
88
|
+
```bash
|
|
89
|
+
npx @umeshindu222/apisnap \
|
|
90
|
+
-H "Authorization: Bearer TOKEN" \
|
|
91
|
+
-H "x-tenant-id: acme" \
|
|
92
|
+
-H "x-api-version: 2"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Skip specific protected routes
|
|
96
|
+
```javascript
|
|
97
|
+
// In your server — skip routes you don't want tested:
|
|
98
|
+
apisnap.init(app, {
|
|
99
|
+
skip: ['/admin', '/internal', '/webhooks']
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Use a config file (recommended for teams)
|
|
104
|
+
|
|
105
|
+
Create `.apisnaprc.json` in your project root:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"port": "3000",
|
|
110
|
+
"slow": "300",
|
|
111
|
+
"headers": [
|
|
112
|
+
"Authorization: Bearer YOUR_DEV_TOKEN",
|
|
113
|
+
"x-api-key: YOUR_KEY"
|
|
114
|
+
],
|
|
115
|
+
"cookie": "sessionId=dev-session-abc",
|
|
116
|
+
"params": {
|
|
117
|
+
"id": "42",
|
|
118
|
+
"slug": "hello-world",
|
|
119
|
+
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then just run:
|
|
57
125
|
```bash
|
|
58
126
|
npx @umeshindu222/apisnap
|
|
59
127
|
```
|
|
60
128
|
|
|
61
129
|
---
|
|
62
130
|
|
|
63
|
-
##
|
|
131
|
+
## Middleware Placement (Important!)
|
|
132
|
+
|
|
133
|
+
The order of middleware in Express matters:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
// ✅ CORRECT — apisnap can bypass auth because it registers first
|
|
137
|
+
app.use(express.json());
|
|
138
|
+
apisnap.init(app); // ← BEFORE auth middleware
|
|
139
|
+
app.use(authMiddleware); // ← AFTER apisnap.init
|
|
140
|
+
|
|
141
|
+
// ❌ WRONG — auth blocks the discovery endpoint
|
|
142
|
+
app.use(authMiddleware); // ← BEFORE apisnap
|
|
143
|
+
apisnap.init(app); // discovery endpoint gets blocked!
|
|
144
|
+
```
|
|
64
145
|
|
|
65
|
-
|
|
146
|
+
If you **must** put auth before apisnap, manually whitelist the discovery path:
|
|
66
147
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
148
|
+
```javascript
|
|
149
|
+
app.use((req, res, next) => {
|
|
150
|
+
if (req.path === '/__apisnap_discovery') return next(); // bypass
|
|
151
|
+
return authMiddleware(req, res, next);
|
|
152
|
+
});
|
|
153
|
+
```
|
|
71
154
|
|
|
72
155
|
---
|
|
73
156
|
|
|
74
|
-
##
|
|
157
|
+
## CLI Reference
|
|
75
158
|
|
|
76
159
|
```bash
|
|
77
160
|
npx @umeshindu222/apisnap [options]
|
|
78
161
|
```
|
|
79
162
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
163
|
+
| Option | Description | Default |
|
|
164
|
+
|--------|-------------|---------|
|
|
165
|
+
| `-p, --port <n>` | Port your server runs on | `3000` |
|
|
166
|
+
| `-H, --header <str>` | Add auth header (repeatable) | — |
|
|
167
|
+
| `-c, --cookie <str>` | Cookie string for session auth | — |
|
|
168
|
+
| `-s, --slow <n>` | Slow threshold in ms | `200` |
|
|
169
|
+
| `-t, --timeout <n>` | Request timeout in ms | `5000` |
|
|
170
|
+
| `-r, --retry <n>` | Retry failed requests N times | `0` |
|
|
171
|
+
| `-e, --export <file>` | Export JSON report | — |
|
|
172
|
+
| `--html <file>` | Export HTML report | — |
|
|
173
|
+
| `--only <methods>` | Filter methods (e.g. `GET,POST`) | — |
|
|
174
|
+
| `--base-url <url>` | Override base URL (for staging) | `localhost` |
|
|
175
|
+
| `--params <json>` | Path param overrides as JSON | — |
|
|
176
|
+
| `--fail-on-slow` | Exit code 1 if slow routes found | `false` |
|
|
84
177
|
|
|
85
178
|
---
|
|
86
179
|
|
|
87
|
-
##
|
|
180
|
+
## Examples
|
|
88
181
|
|
|
89
|
-
### Basic
|
|
182
|
+
### Basic
|
|
90
183
|
```bash
|
|
91
184
|
npx @umeshindu222/apisnap --port 3000
|
|
92
185
|
```
|
|
93
186
|
|
|
94
|
-
### With
|
|
187
|
+
### With JWT auth
|
|
95
188
|
```bash
|
|
96
|
-
npx @umeshindu222/apisnap
|
|
189
|
+
npx @umeshindu222/apisnap -p 3000 -H "Authorization: Bearer eyJhbGci..."
|
|
97
190
|
```
|
|
98
191
|
|
|
99
|
-
### Custom
|
|
192
|
+
### Custom path params (for routes like `/users/:id/posts/:postId`)
|
|
100
193
|
```bash
|
|
101
|
-
npx @umeshindu222/apisnap --
|
|
194
|
+
npx @umeshindu222/apisnap --params '{"id":"42","postId":"7"}'
|
|
102
195
|
```
|
|
103
196
|
|
|
104
|
-
###
|
|
197
|
+
### Test only GET routes
|
|
105
198
|
```bash
|
|
106
|
-
npx @umeshindu222/apisnap --
|
|
107
|
-
# Creates: my-report.json
|
|
199
|
+
npx @umeshindu222/apisnap --only GET
|
|
108
200
|
```
|
|
109
201
|
|
|
110
|
-
###
|
|
202
|
+
### Test staging server
|
|
111
203
|
```bash
|
|
112
|
-
npx @umeshindu222/apisnap --
|
|
204
|
+
npx @umeshindu222/apisnap --base-url https://staging.myapp.com -H "Authorization: Bearer TOKEN"
|
|
113
205
|
```
|
|
114
206
|
|
|
115
|
-
|
|
207
|
+
### Generate HTML report
|
|
208
|
+
```bash
|
|
209
|
+
npx @umeshindu222/apisnap --html report
|
|
210
|
+
|
|
211
|
+
# Mac
|
|
212
|
+
# open report.html
|
|
116
213
|
|
|
117
|
-
|
|
214
|
+
# Windows
|
|
215
|
+
# start report.html
|
|
118
216
|
|
|
217
|
+
# Linux
|
|
218
|
+
# xdg-open report.html
|
|
119
219
|
```
|
|
120
|
-
📸 APISnap v1.0.0
|
|
121
|
-
Slow threshold: 200ms
|
|
122
220
|
|
|
123
|
-
|
|
221
|
+
### CI/CD — fail pipeline on any broken endpoint
|
|
222
|
+
```bash
|
|
223
|
+
npx @umeshindu222/apisnap --export ci-report && echo "All healthy!"
|
|
224
|
+
# Exit code 1 if any endpoint fails
|
|
225
|
+
```
|
|
124
226
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
227
|
+
### Full power
|
|
228
|
+
```bash
|
|
229
|
+
npx @umeshindu222/apisnap \
|
|
230
|
+
-p 5000 \
|
|
231
|
+
-H "Authorization: Bearer TOKEN" \
|
|
232
|
+
-H "x-api-key: SECRET" \
|
|
233
|
+
--cookie "sessionId=abc" \
|
|
234
|
+
--slow 300 \
|
|
235
|
+
--retry 2 \
|
|
236
|
+
--html report \
|
|
237
|
+
--export report \
|
|
238
|
+
--fail-on-slow
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Sample Output
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
📸 APISnap v2.0.0
|
|
247
|
+
Target: http://localhost:3000
|
|
248
|
+
Slow: >200ms
|
|
249
|
+
Timeout: 5000ms
|
|
250
|
+
Headers: {"Authorization":"Bearer ••••••"}
|
|
251
|
+
|
|
252
|
+
✔ Connected! Found 6 endpoints to test.
|
|
253
|
+
|
|
254
|
+
✔ GET /health [200] 3ms
|
|
255
|
+
✔ GET /users [200] 12ms
|
|
256
|
+
✔ POST /users [200] 8ms
|
|
257
|
+
✔ GET /users/1 [200] 15ms
|
|
258
|
+
⚠️ GET /reports [200] 543ms ← slow!
|
|
259
|
+
✖ DELETE /users/1 [401]
|
|
260
|
+
💡 Hint: 401 Unauthorized — try adding -H "Authorization: Bearer YOUR_TOKEN"
|
|
131
261
|
|
|
132
262
|
📊 Summary:
|
|
133
263
|
✅ Passed: 5
|
|
134
264
|
❌ Failed: 1
|
|
135
265
|
⚠️ Slow: 1 (>200ms)
|
|
266
|
+
⏱ Avg: 100ms
|
|
267
|
+
🕐 Total: 600ms
|
|
136
268
|
|
|
137
269
|
⚠️ Some endpoints are unhealthy!
|
|
138
270
|
```
|
|
139
271
|
|
|
140
272
|
---
|
|
141
273
|
|
|
142
|
-
##
|
|
274
|
+
## HTML Report
|
|
275
|
+
|
|
276
|
+
`--html report` generates a beautiful standalone HTML file:
|
|
277
|
+
|
|
278
|
+
- Pass rate progress bar
|
|
279
|
+
- Color-coded result table
|
|
280
|
+
- Per-endpoint timing, status, retry count
|
|
281
|
+
- No external dependencies — works offline
|
|
143
282
|
|
|
144
|
-
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## JSON Report Format
|
|
145
286
|
|
|
146
287
|
```json
|
|
147
288
|
{
|
|
148
289
|
"tool": "APISnap",
|
|
149
|
-
"
|
|
290
|
+
"version": "2.0.0",
|
|
291
|
+
"generatedAt": "2026-03-08T10:00:00.000Z",
|
|
150
292
|
"config": { "port": "3000", "slowThreshold": 200 },
|
|
151
|
-
"summary": {
|
|
293
|
+
"summary": {
|
|
294
|
+
"total": 6, "passed": 5, "failed": 1,
|
|
295
|
+
"slow": 1, "avgDuration": 100, "totalDuration": 600
|
|
296
|
+
},
|
|
152
297
|
"results": [
|
|
153
|
-
{ "method": "GET", "path": "/
|
|
154
|
-
{ "method": "GET", "path": "/users", "status": 200, "duration": 12, "success": true, "slow": false },
|
|
155
|
-
{ "method": "GET", "path": "/reports", "status": 200, "duration": 543,"success": true, "slow": true }
|
|
298
|
+
{ "method": "GET", "path": "/users", "status": 200, "duration": 12, "success": true, "slow": false, "retries": 0 }
|
|
156
299
|
]
|
|
157
300
|
}
|
|
158
301
|
```
|
|
159
302
|
|
|
160
|
-
> **CI/CD tip:**
|
|
303
|
+
> **CI/CD tip:** Check `summary.failed > 0` to fail your build.
|
|
161
304
|
|
|
162
305
|
---
|
|
163
306
|
|
|
164
|
-
##
|
|
307
|
+
## How It Works
|
|
165
308
|
|
|
166
|
-
|
|
309
|
+
1. **Middleware** — `apisnap.init(app)` registers `/__apisnap_discovery` and patches `app.use` so global auth middleware skips the discovery path automatically.
|
|
167
310
|
|
|
168
|
-
|
|
311
|
+
2. **CLI** — Calls the discovery endpoint, gets the full route map, then pings each route with your headers/cookies. Smart defaults replace `:id` → `1`, `:uuid` → a valid UUID, `:slug` → `"example"`, etc.
|
|
169
312
|
|
|
170
|
-
|
|
313
|
+
3. **Reports** — Results are collected and can be exported as JSON (for CI/CD) or a self-contained HTML file (for humans).
|
|
171
314
|
|
|
172
315
|
---
|
|
173
316
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
Contributions, issues and feature requests are welcome!
|
|
317
|
+
## Contributing
|
|
177
318
|
|
|
178
|
-
1. Fork
|
|
179
|
-
2. Create your feature branch: `git checkout -b feat/amazing-feature`
|
|
180
|
-
3. Commit your changes: `git commit -m 'feat: add amazing feature'`
|
|
181
|
-
4. Push to the branch: `git push origin feat/amazing-feature`
|
|
182
|
-
5. Open a Pull Request
|
|
319
|
+
1. Fork → `git checkout -b feat/amazing` → commit → push → PR
|
|
183
320
|
|
|
184
321
|
---
|
|
185
322
|
|
|
186
|
-
##
|
|
323
|
+
## License
|
|
187
324
|
|
|
188
325
|
MIT © [Umesh Induranga](https://github.com/Umeshinduranga)
|
package/dist/core/runner.js
CHANGED
|
@@ -4,191 +4,382 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
7
8
|
const axios_1 = __importDefault(require("axios"));
|
|
8
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
10
|
const ora_1 = __importDefault(require("ora"));
|
|
10
11
|
const commander_1 = require("commander");
|
|
11
|
-
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const program = new commander_1.Command();
|
|
13
13
|
const { version } = require('../../package.json');
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.option('-H, --header <string>', 'One-time header (Key:Value)')
|
|
20
|
-
.option('-s, --slow <number>', 'Override slow threshold (ms)')
|
|
21
|
-
.option('-e, --export <filename>', 'Export results to JSON file (e.g., report.json)')
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
let config = { port: 3000, slowThreshold: 200, headers: {} };
|
|
24
|
-
// Smart Merge: Load shared, then override with local
|
|
25
|
-
['apisnap.json', 'apisnap.local.json'].forEach(file => {
|
|
26
|
-
const filePath = path_1.default.join(process.cwd(), file);
|
|
14
|
+
// ─── Config File Loader ───────────────────────────────────────────────────────
|
|
15
|
+
function loadConfigFile() {
|
|
16
|
+
const configNames = ['.apisnaprc', '.apisnaprc.json', 'apisnap.config.json'];
|
|
17
|
+
for (const name of configNames) {
|
|
18
|
+
const filePath = path_1.default.resolve(process.cwd(), name);
|
|
27
19
|
if (fs_1.default.existsSync(filePath)) {
|
|
28
20
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
// Strip BOM if present (fixes Windows PowerShell encoding issue)
|
|
22
|
+
let raw = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
23
|
+
raw = raw.replace(/^\uFEFF/, '');
|
|
24
|
+
raw = raw.trim();
|
|
25
|
+
console.log(chalk_1.default.gray(` Config: ${name}\n`));
|
|
26
|
+
return JSON.parse(raw);
|
|
35
27
|
}
|
|
36
28
|
catch (e) {
|
|
37
|
-
console.
|
|
29
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not parse config file: ${name}`));
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
|
-
});
|
|
41
|
-
// Final Priority: CLI Flags always win
|
|
42
|
-
const port = options.port || config.port;
|
|
43
|
-
const slowThreshold = options.slow ? parseInt(options.slow) : config.slowThreshold;
|
|
44
|
-
const finalHeaders = { ...config.headers };
|
|
45
|
-
if (options.header) {
|
|
46
|
-
const [key, ...val] = options.header.split(':');
|
|
47
|
-
finalHeaders[key.trim()] = val.join(':').trim();
|
|
48
32
|
}
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
// ─── Header Parser ─────────────────────────────────────────────────────────
|
|
36
|
+
function parseHeaders(headerArgs) {
|
|
37
|
+
const headers = {};
|
|
38
|
+
for (const h of headerArgs) {
|
|
39
|
+
const colonIdx = h.indexOf(':');
|
|
40
|
+
if (colonIdx > 0) {
|
|
41
|
+
const key = h.slice(0, colonIdx).trim();
|
|
42
|
+
const value = h.slice(colonIdx + 1).trim();
|
|
43
|
+
headers[key] = value;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.warn(chalk_1.default.yellow(`⚠️ Skipping malformed header: "${h}" (expected "Key: Value")`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return headers;
|
|
50
|
+
}
|
|
51
|
+
// ─── Smart Path Param Replacement ────────────────────────────────────────────
|
|
52
|
+
function replacePath(rawPath, paramMap = {}) {
|
|
53
|
+
return rawPath.replace(/:([a-zA-Z0-9_]+)/g, (_, param) => {
|
|
54
|
+
if (paramMap[param])
|
|
55
|
+
return paramMap[param];
|
|
56
|
+
// Smart defaults based on param name
|
|
57
|
+
if (/id$/i.test(param))
|
|
58
|
+
return '1';
|
|
59
|
+
if (/slug$/i.test(param))
|
|
60
|
+
return 'example';
|
|
61
|
+
if (/uuid$/i.test(param))
|
|
62
|
+
return '00000000-0000-0000-0000-000000000001';
|
|
63
|
+
if (/name$/i.test(param))
|
|
64
|
+
return 'test';
|
|
65
|
+
if (/token$/i.test(param))
|
|
66
|
+
return 'abc123';
|
|
67
|
+
if (/page$/i.test(param))
|
|
68
|
+
return '1';
|
|
69
|
+
if (/limit$/i.test(param))
|
|
70
|
+
return '10';
|
|
71
|
+
return '1'; // fallback
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// ─── HTML Report Generator ────────────────────────────────────────────────────
|
|
75
|
+
function generateHTMLReport(data) {
|
|
76
|
+
const passRate = data.summary.total > 0
|
|
77
|
+
? Math.round((data.summary.passed / data.summary.total) * 100)
|
|
78
|
+
: 0;
|
|
79
|
+
const rowColor = (r) => {
|
|
80
|
+
if (!r.success)
|
|
81
|
+
return '#fee2e2';
|
|
82
|
+
if (r.slow)
|
|
83
|
+
return '#fef9c3';
|
|
84
|
+
return '#f0fdf4';
|
|
85
|
+
};
|
|
86
|
+
const rows = data.results.map(r => `
|
|
87
|
+
<tr style="background:${rowColor(r)}">
|
|
88
|
+
<td><span class="badge badge-${r.method.toLowerCase()}">${r.method}</span></td>
|
|
89
|
+
<td><code>${r.path}</code></td>
|
|
90
|
+
<td>${r.success
|
|
91
|
+
? `<span class="ok">✔ ${r.status}</span>`
|
|
92
|
+
: `<span class="fail">✖ ${r.status || 'ERR'}</span>`}</td>
|
|
93
|
+
<td>${r.slow ? `<span class="slow">⚠️ ${r.duration}ms</span>` : `${r.duration}ms`}</td>
|
|
94
|
+
<td>${r.retries > 0 ? `${r.retries} retry` : '—'}</td>
|
|
95
|
+
<td>${r.error ? `<span class="errtext">${r.error}</span>` : '—'}</td>
|
|
96
|
+
</tr>
|
|
97
|
+
`).join('');
|
|
98
|
+
return `<!DOCTYPE html>
|
|
99
|
+
<html lang="en">
|
|
100
|
+
<head>
|
|
101
|
+
<meta charset="UTF-8"/>
|
|
102
|
+
<title>APISnap Report — ${data.generatedAt}</title>
|
|
103
|
+
<style>
|
|
104
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
105
|
+
body{font-family:'Segoe UI',system-ui,sans-serif;background:#f8fafc;color:#1e293b;padding:2rem}
|
|
106
|
+
h1{font-size:1.8rem;margin-bottom:.25rem}
|
|
107
|
+
.sub{color:#64748b;font-size:.9rem;margin-bottom:2rem}
|
|
108
|
+
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem;margin-bottom:2rem}
|
|
109
|
+
.card{background:#fff;border-radius:12px;padding:1.2rem;box-shadow:0 1px 4px rgba(0,0,0,.08);text-align:center}
|
|
110
|
+
.card .num{font-size:2rem;font-weight:700}
|
|
111
|
+
.card .lbl{font-size:.8rem;color:#64748b;margin-top:.25rem}
|
|
112
|
+
.green{color:#16a34a}.red{color:#dc2626}.yellow{color:#ca8a04}.blue{color:#2563eb}
|
|
113
|
+
table{width:100%;border-collapse:collapse;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 4px rgba(0,0,0,.08)}
|
|
114
|
+
th{background:#1e293b;color:#f8fafc;padding:.8rem 1rem;text-align:left;font-size:.85rem}
|
|
115
|
+
td{padding:.75rem 1rem;font-size:.875rem;border-bottom:1px solid #f1f5f9}
|
|
116
|
+
.badge{display:inline-block;padding:.15rem .5rem;border-radius:6px;font-weight:600;font-size:.75rem;color:#fff}
|
|
117
|
+
.badge-get{background:#2563eb}.badge-post{background:#16a34a}.badge-put{background:#d97706}
|
|
118
|
+
.badge-delete{background:#dc2626}.badge-patch{background:#7c3aed}
|
|
119
|
+
.ok{color:#16a34a;font-weight:600}.fail{color:#dc2626;font-weight:600}.slow{color:#ca8a04;font-weight:600}
|
|
120
|
+
.errtext{color:#dc2626;font-size:.8rem}
|
|
121
|
+
.progress{background:#e2e8f0;border-radius:999px;height:10px;margin:1rem 0}
|
|
122
|
+
.progress-bar{background:#16a34a;height:10px;border-radius:999px;transition:width .3s}
|
|
123
|
+
footer{margin-top:2rem;color:#94a3b8;font-size:.8rem;text-align:center}
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<h1>📸 APISnap Health Report</h1>
|
|
128
|
+
<p class="sub">Generated: ${data.generatedAt} | Port: ${data.config.port} | v${data.version}</p>
|
|
129
|
+
|
|
130
|
+
<div class="cards">
|
|
131
|
+
<div class="card"><div class="num blue">${data.summary.total}</div><div class="lbl">Total Endpoints</div></div>
|
|
132
|
+
<div class="card"><div class="num green">${data.summary.passed}</div><div class="lbl">Passed</div></div>
|
|
133
|
+
<div class="card"><div class="num red">${data.summary.failed}</div><div class="lbl">Failed</div></div>
|
|
134
|
+
<div class="card"><div class="num yellow">${data.summary.slow}</div><div class="lbl">Slow (>${data.config.slowThreshold}ms)</div></div>
|
|
135
|
+
<div class="card"><div class="num blue">${data.summary.avgDuration}ms</div><div class="lbl">Avg Response</div></div>
|
|
136
|
+
<div class="card"><div class="num ${passRate === 100 ? 'green' : passRate >= 80 ? 'yellow' : 'red'}">${passRate}%</div><div class="lbl">Pass Rate</div></div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="progress"><div class="progress-bar" style="width:${passRate}%"></div></div>
|
|
140
|
+
|
|
141
|
+
<table>
|
|
142
|
+
<thead>
|
|
143
|
+
<tr><th>Method</th><th>Path</th><th>Status</th><th>Duration</th><th>Retries</th><th>Error</th></tr>
|
|
144
|
+
</thead>
|
|
145
|
+
<tbody>${rows}</tbody>
|
|
146
|
+
</table>
|
|
147
|
+
|
|
148
|
+
<footer>APISnap v${data.version} — MIT License</footer>
|
|
149
|
+
</body>
|
|
150
|
+
</html>`;
|
|
151
|
+
}
|
|
152
|
+
// ─── Main CLI ─────────────────────────────────────────────────────────────────
|
|
153
|
+
program
|
|
154
|
+
.name('apisnap')
|
|
155
|
+
.description('Instant API health-check CLI for Express.js')
|
|
156
|
+
.version(version)
|
|
157
|
+
.option('-p, --port <number>', 'Port your server is running on')
|
|
158
|
+
.option('-H, --header <string>', 'Custom header — can be used multiple times (e.g. -H "Authorization: Bearer TOKEN" -H "x-api-key: SECRET")', collect, [])
|
|
159
|
+
.option('-c, --cookie <string>', 'Cookie string (e.g. "sessionId=abc; token=xyz")')
|
|
160
|
+
.option('-s, --slow <number>', 'Slow response threshold in ms')
|
|
161
|
+
.option('-t, --timeout <number>', 'Request timeout in ms')
|
|
162
|
+
.option('-r, --retry <number>', 'Retry failed requests N times')
|
|
163
|
+
.option('-e, --export <filename>', 'Export JSON report (e.g. report)')
|
|
164
|
+
.option('--html <filename>', 'Export HTML report (e.g. report)')
|
|
165
|
+
.option('--only <methods>', 'Only test specific methods (e.g. "GET,POST")')
|
|
166
|
+
.option('--base-url <url>', 'Override base URL (e.g. https://staging.myapp.com)')
|
|
167
|
+
.option('--params <json>', 'JSON map of param overrides (e.g. \'{"id":"42"}\')')
|
|
168
|
+
.option('--fail-on-slow', 'Exit with code 1 if any slow routes are found')
|
|
169
|
+
.action(async (options) => {
|
|
170
|
+
// Merge config file with CLI options (CLI takes precedence)
|
|
171
|
+
const fileConfig = loadConfigFile();
|
|
172
|
+
const mergedOptions = { ...fileConfig, ...options };
|
|
173
|
+
const port = mergedOptions.port || '3000';
|
|
174
|
+
const slowThreshold = parseInt(mergedOptions.slow || '200');
|
|
175
|
+
const timeout = parseInt(mergedOptions.timeout || '5000');
|
|
176
|
+
const retryCount = parseInt(mergedOptions.retry || '0');
|
|
177
|
+
const onlyMethods = mergedOptions.only
|
|
178
|
+
? mergedOptions.only.split(',').map((m) => m.trim().toUpperCase())
|
|
179
|
+
: null;
|
|
180
|
+
const paramOverrides = mergedOptions.params
|
|
181
|
+
? JSON.parse(mergedOptions.params)
|
|
182
|
+
: (fileConfig.params || {});
|
|
183
|
+
const baseUrl = mergedOptions.baseUrl || mergedOptions['base-url'] || `http://localhost:${port}`;
|
|
49
184
|
const discoveryUrl = `http://localhost:${port}/__apisnap_discovery`;
|
|
50
|
-
|
|
185
|
+
// Build headers
|
|
186
|
+
const headerArgs = [
|
|
187
|
+
...(Array.isArray(mergedOptions.header) ? mergedOptions.header : []),
|
|
188
|
+
...(Array.isArray(fileConfig.headers) ? fileConfig.headers : []),
|
|
189
|
+
];
|
|
190
|
+
const customHeaders = {
|
|
191
|
+
...parseHeaders(headerArgs),
|
|
192
|
+
'User-Agent': `APISnap/${version}`,
|
|
193
|
+
};
|
|
194
|
+
if (mergedOptions.cookie) {
|
|
195
|
+
customHeaders['Cookie'] = mergedOptions.cookie;
|
|
196
|
+
}
|
|
197
|
+
// ── Banner ──────────────────────────────────────────────────────────────
|
|
51
198
|
console.log(chalk_1.default.bold.cyan(`\n📸 APISnap v${version}`));
|
|
52
|
-
console.log(chalk_1.default.gray(`
|
|
53
|
-
|
|
199
|
+
console.log(chalk_1.default.gray(` Target: ${baseUrl}`));
|
|
200
|
+
console.log(chalk_1.default.gray(` Slow: >${slowThreshold}ms`));
|
|
201
|
+
console.log(chalk_1.default.gray(` Timeout: ${timeout}ms`));
|
|
202
|
+
if (retryCount > 0)
|
|
203
|
+
console.log(chalk_1.default.gray(` Retries: ${retryCount}`));
|
|
204
|
+
if (Object.keys(customHeaders).filter(k => k !== 'User-Agent').length > 0) {
|
|
205
|
+
const safeHeaders = { ...customHeaders };
|
|
206
|
+
// Mask auth tokens in output
|
|
207
|
+
Object.keys(safeHeaders).forEach(k => {
|
|
208
|
+
if (/auth|token|key|secret|cookie/i.test(k)) {
|
|
209
|
+
safeHeaders[k] = safeHeaders[k].slice(0, 8) + '••••••';
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
delete safeHeaders['User-Agent'];
|
|
213
|
+
console.log(chalk_1.default.gray(` Headers: ${JSON.stringify(safeHeaders)}`));
|
|
214
|
+
}
|
|
215
|
+
if (onlyMethods)
|
|
216
|
+
console.log(chalk_1.default.gray(` Filter: ${onlyMethods.join(', ')}`));
|
|
217
|
+
console.log();
|
|
218
|
+
const spinner = (0, ora_1.default)('Connecting to discovery endpoint...').start();
|
|
219
|
+
const results = [];
|
|
54
220
|
try {
|
|
55
|
-
//
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
221
|
+
// ── Discovery ───────────────────────────────────────────────────────
|
|
222
|
+
const discovery = await axios_1.default.get(discoveryUrl, { timeout: 5000 });
|
|
223
|
+
let { endpoints } = discovery.data;
|
|
224
|
+
// Filter by method if --only flag provided
|
|
225
|
+
if (onlyMethods) {
|
|
226
|
+
endpoints = endpoints.filter((e) => e.methods.some((m) => onlyMethods.includes(m)));
|
|
227
|
+
}
|
|
228
|
+
spinner.succeed(chalk_1.default.green(`Connected! Found ${endpoints.length} endpoint${endpoints.length !== 1 ? 's' : ''} to test.\n`));
|
|
229
|
+
let passed = 0, failed = 0, slow = 0;
|
|
230
|
+
const allDurations = [];
|
|
231
|
+
// ── Test Each Endpoint ───────────────────────────────────────────────
|
|
64
232
|
for (const endpoint of endpoints) {
|
|
65
|
-
const method = endpoint.methods[0];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
path = path.replace(/:[a-zA-Z0-9]+/g, '1');
|
|
70
|
-
}
|
|
71
|
-
const fullUrl = `http://localhost:${port}${path}`;
|
|
72
|
-
const testSpinner = (0, ora_1.default)(`Testing ${chalk_1.default.bold(method)} ${path}`).start();
|
|
73
|
-
// Step 8: Initialize result object for this endpoint
|
|
233
|
+
const method = endpoint.methods[0]; // primary method
|
|
234
|
+
const rawPath = endpoint.path;
|
|
235
|
+
const resolvedPath = replacePath(rawPath, paramOverrides);
|
|
236
|
+
const fullUrl = `${baseUrl}${resolvedPath}`;
|
|
74
237
|
const testResult = {
|
|
75
|
-
method,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
status: 0,
|
|
79
|
-
duration: 0,
|
|
80
|
-
success: false,
|
|
81
|
-
slow: false,
|
|
238
|
+
method, path: rawPath, fullUrl,
|
|
239
|
+
status: 0, statusText: '', duration: 0,
|
|
240
|
+
success: false, slow: false, retries: 0,
|
|
82
241
|
};
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
242
|
+
const testSpinner = (0, ora_1.default)({ text: `${chalk_1.default.bold(method.padEnd(7))} ${chalk_1.default.dim(rawPath)}`, prefixText: ' ' }).start();
|
|
243
|
+
let lastError = null;
|
|
244
|
+
let attempt = 0;
|
|
245
|
+
while (attempt <= retryCount) {
|
|
246
|
+
try {
|
|
247
|
+
const start = Date.now();
|
|
248
|
+
const res = await (0, axios_1.default)({
|
|
249
|
+
method,
|
|
250
|
+
url: fullUrl,
|
|
251
|
+
headers: customHeaders,
|
|
252
|
+
timeout,
|
|
253
|
+
validateStatus: () => true, // Don't throw on 4xx/5xx — we judge ourselves
|
|
254
|
+
});
|
|
255
|
+
const duration = Date.now() - start;
|
|
256
|
+
testResult.duration = duration;
|
|
257
|
+
testResult.status = res.status;
|
|
258
|
+
testResult.statusText = res.statusText;
|
|
259
|
+
testResult.retries = attempt;
|
|
260
|
+
testResult.success = res.status < 400;
|
|
261
|
+
testResult.slow = duration > slowThreshold;
|
|
262
|
+
allDurations.push(duration);
|
|
263
|
+
if (testResult.success) {
|
|
264
|
+
const durationStr = testResult.slow
|
|
265
|
+
? chalk_1.default.yellow.bold(`${duration}ms ← slow!`)
|
|
266
|
+
: chalk_1.default.gray(`${duration}ms`);
|
|
267
|
+
const msg = `${chalk_1.default.bold(method.padEnd(7))} ${chalk_1.default.white(rawPath.padEnd(35))} ` +
|
|
268
|
+
`${chalk_1.default.green(`[${res.status}]`)} ${durationStr}`;
|
|
269
|
+
if (testResult.slow) {
|
|
270
|
+
testSpinner.warn(msg);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
testSpinner.succeed(msg);
|
|
274
|
+
}
|
|
275
|
+
passed++;
|
|
276
|
+
if (testResult.slow)
|
|
277
|
+
slow++;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
testSpinner.fail(`${chalk_1.default.bold(method.padEnd(7))} ${chalk_1.default.white(rawPath.padEnd(35))} ` +
|
|
281
|
+
`${chalk_1.default.red(`[${res.status} ${res.statusText}]`)} ${chalk_1.default.gray(`${duration}ms`)}`);
|
|
282
|
+
// Helpful hint for auth errors
|
|
283
|
+
if (res.status === 401) {
|
|
284
|
+
console.log(chalk_1.default.yellow(` 💡 Hint: 401 Unauthorized — try adding -H "Authorization: Bearer YOUR_TOKEN" or --cookie "sessionId=abc"`));
|
|
285
|
+
}
|
|
286
|
+
else if (res.status === 403) {
|
|
287
|
+
console.log(chalk_1.default.yellow(` 💡 Hint: 403 Forbidden — your token may lack permission for this route`));
|
|
288
|
+
}
|
|
289
|
+
else if (res.status === 404) {
|
|
290
|
+
console.log(chalk_1.default.yellow(` 💡 Hint: 404 Not Found — path param replacement may need --params '{"id":"YOUR_ID"}'`));
|
|
291
|
+
}
|
|
292
|
+
failed++;
|
|
293
|
+
}
|
|
294
|
+
lastError = null;
|
|
295
|
+
break; // success, stop retrying
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
lastError = err;
|
|
299
|
+
attempt++;
|
|
300
|
+
if (attempt <= retryCount) {
|
|
301
|
+
await new Promise(r => setTimeout(r, 500 * attempt)); // backoff
|
|
302
|
+
}
|
|
108
303
|
}
|
|
109
|
-
testSpinner.succeed(`${statusIcon} ${chalk_1.default.bold(method)} ${chalk_1.default.white(path)} ` +
|
|
110
|
-
`${chalk_1.default.green(`[${res.status} OK]`)} ` +
|
|
111
|
-
`${durationColor(`${duration}ms`)}`);
|
|
112
|
-
passed++;
|
|
113
304
|
}
|
|
114
|
-
|
|
115
|
-
testResult.status = err.response?.status || 500;
|
|
305
|
+
if (lastError) {
|
|
116
306
|
testResult.success = false;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
307
|
+
testResult.retries = attempt - 1;
|
|
308
|
+
testResult.error = lastError.code === 'ECONNABORTED' ? 'Timeout' : lastError.message;
|
|
309
|
+
testSpinner.fail(`${chalk_1.default.bold(method.padEnd(7))} ${chalk_1.default.white(rawPath.padEnd(35))} ` +
|
|
310
|
+
chalk_1.default.red(`[${testResult.error}]`));
|
|
120
311
|
failed++;
|
|
121
312
|
}
|
|
122
|
-
results.push(testResult);
|
|
313
|
+
results.push(testResult);
|
|
123
314
|
}
|
|
124
|
-
// Summary
|
|
315
|
+
// ── Summary ──────────────────────────────────────────────────────────
|
|
316
|
+
const avgDuration = allDurations.length > 0
|
|
317
|
+
? Math.round(allDurations.reduce((a, b) => a + b, 0) / allDurations.length)
|
|
318
|
+
: 0;
|
|
319
|
+
const totalDuration = allDurations.reduce((a, b) => a + b, 0);
|
|
125
320
|
console.log(chalk_1.default.bold('\n📊 Summary:'));
|
|
126
|
-
console.log(chalk_1.default.green(
|
|
127
|
-
console.log(chalk_1.default.red(
|
|
128
|
-
console.log(chalk_1.default.yellow(
|
|
321
|
+
console.log(` ${chalk_1.default.green('✅ Passed: ')} ${chalk_1.default.bold(passed)}`);
|
|
322
|
+
console.log(` ${chalk_1.default.red('❌ Failed: ')} ${chalk_1.default.bold(failed)}`);
|
|
323
|
+
console.log(` ${chalk_1.default.yellow('⚠️ Slow: ')} ${chalk_1.default.bold(slow)} (>${slowThreshold}ms)`);
|
|
324
|
+
console.log(` ${chalk_1.default.cyan('⏱ Avg: ')} ${chalk_1.default.bold(avgDuration + 'ms')}`);
|
|
325
|
+
console.log(` ${chalk_1.default.cyan('🕐 Total: ')} ${chalk_1.default.bold(totalDuration + 'ms')}`);
|
|
129
326
|
if (failed > 0) {
|
|
130
327
|
console.log(chalk_1.default.red.bold('\n⚠️ Some endpoints are unhealthy!'));
|
|
131
328
|
}
|
|
132
329
|
else if (slow > 0) {
|
|
133
|
-
console.log(chalk_1.default.yellow.bold('\n🐢 All
|
|
330
|
+
console.log(chalk_1.default.yellow.bold('\n🐢 All alive, but some routes are slow!'));
|
|
134
331
|
}
|
|
135
332
|
else {
|
|
136
333
|
console.log(chalk_1.default.green.bold('\n✨ All systems nominal!'));
|
|
137
334
|
}
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
335
|
+
// ── Auth Troubleshooting Summary ─────────────────────────────────────
|
|
336
|
+
const authFailures = results.filter(r => r.status === 401 || r.status === 403);
|
|
337
|
+
if (authFailures.length > 0 && !headerArgs.length && !mergedOptions.cookie) {
|
|
338
|
+
console.log(chalk_1.default.bgYellow.black.bold('\n🔐 Auth Help'));
|
|
339
|
+
console.log(chalk_1.default.yellow(' You have ' + authFailures.length + ' auth failure(s) and no credentials were provided.'));
|
|
340
|
+
console.log(chalk_1.default.yellow(' Solutions:'));
|
|
341
|
+
console.log(chalk_1.default.gray(' JWT: apisnap -H "Authorization: Bearer YOUR_JWT_TOKEN"'));
|
|
342
|
+
console.log(chalk_1.default.gray(' API Key: apisnap -H "x-api-key: YOUR_KEY"'));
|
|
343
|
+
console.log(chalk_1.default.gray(' Cookie: apisnap --cookie "sessionId=abc123"'));
|
|
344
|
+
console.log(chalk_1.default.gray(' Multi: apisnap -H "Authorization: Bearer TOKEN" -H "x-tenant: acme"'));
|
|
345
|
+
console.log(chalk_1.default.gray(' Config: create .apisnaprc.json (see README)\n'));
|
|
346
|
+
}
|
|
347
|
+
// ── Exports ──────────────────────────────────────────────────────────
|
|
348
|
+
const reportData = {
|
|
349
|
+
tool: 'APISnap', version,
|
|
350
|
+
generatedAt: new Date().toISOString(),
|
|
351
|
+
config: { port, baseUrl, slowThreshold, timeout, headers: Object.keys(customHeaders).filter(k => k !== 'User-Agent') },
|
|
352
|
+
summary: { total: endpoints.length, passed, failed, slow, avgDuration, totalDuration },
|
|
353
|
+
results,
|
|
354
|
+
};
|
|
355
|
+
if (mergedOptions.export) {
|
|
356
|
+
const filePath = mergedOptions.export.endsWith('.json') ? mergedOptions.export : `${mergedOptions.export}.json`;
|
|
159
357
|
fs_1.default.writeFileSync(filePath, JSON.stringify(reportData, null, 2));
|
|
160
|
-
console.log(chalk_1.default.cyan
|
|
358
|
+
console.log(chalk_1.default.cyan(`\n💾 JSON report → ${chalk_1.default.white(filePath)}`));
|
|
161
359
|
}
|
|
360
|
+
if (mergedOptions.html) {
|
|
361
|
+
const filePath = mergedOptions.html.endsWith('.html') ? mergedOptions.html : `${mergedOptions.html}.html`;
|
|
362
|
+
fs_1.default.writeFileSync(filePath, generateHTMLReport(reportData));
|
|
363
|
+
console.log(chalk_1.default.cyan(`🌐 HTML report → ${chalk_1.default.white(filePath)}`));
|
|
364
|
+
}
|
|
365
|
+
console.log();
|
|
366
|
+
// Exit codes for CI/CD
|
|
367
|
+
const shouldFail = failed > 0 || (mergedOptions['fail-on-slow'] && slow > 0);
|
|
368
|
+
process.exit(shouldFail ? 1 : 0);
|
|
162
369
|
}
|
|
163
370
|
catch (error) {
|
|
164
|
-
spinner.fail(chalk_1.default.red(`
|
|
165
|
-
console.log(chalk_1.default.yellow('
|
|
371
|
+
spinner.fail(chalk_1.default.red(`Cannot reach discovery endpoint: ${discoveryUrl}`));
|
|
372
|
+
console.log(chalk_1.default.yellow('\n Checklist:'));
|
|
373
|
+
console.log(chalk_1.default.gray(' 1. Is your server running? (e.g. node server.js)'));
|
|
374
|
+
console.log(chalk_1.default.gray(' 2. Did you call apisnap.init(app) in your server?'));
|
|
375
|
+
console.log(chalk_1.default.gray(' 3. Is the port correct? (default 3000, use -p PORT)'));
|
|
376
|
+
console.log(chalk_1.default.gray(' 4. Is apisnap.init(app) placed AFTER your routes?\n'));
|
|
166
377
|
process.exit(1);
|
|
167
378
|
}
|
|
168
379
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
const sharedConfig = {
|
|
174
|
-
port: 3000,
|
|
175
|
-
slowThreshold: 200,
|
|
176
|
-
description: "Shared project API settings"
|
|
177
|
-
};
|
|
178
|
-
const localConfig = {
|
|
179
|
-
headers: {
|
|
180
|
-
Authorization: "Bearer YOUR_PRIVATE_TOKEN_HERE"
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
// Create the files in the user's current directory
|
|
184
|
-
fs_1.default.writeFileSync('apisnap.json', JSON.stringify(sharedConfig, null, 2));
|
|
185
|
-
fs_1.default.writeFileSync('apisnap.local.json', JSON.stringify(localConfig, null, 2));
|
|
186
|
-
console.log(chalk_1.default.green.bold('\n✨ APISnap Initialized Successfully!'));
|
|
187
|
-
console.log(chalk_1.default.cyan('Created:'));
|
|
188
|
-
console.log(` 📄 ${chalk_1.default.white('apisnap.json')} (Shared - Push to GitHub)`);
|
|
189
|
-
console.log(` 📄 ${chalk_1.default.yellow('apisnap.local.json')} (Private - DO NOT PUSH)`);
|
|
190
|
-
console.log(chalk_1.default.red.bold('\n⚠️ IMPORTANT SECURITY STEP:'));
|
|
191
|
-
console.log(`Add ${chalk_1.default.yellow.bold('apisnap.local.json')} to your ${chalk_1.default.white('.gitignore')} file now!\n`);
|
|
192
|
-
});
|
|
380
|
+
// Allows -H to be used multiple times
|
|
381
|
+
function collect(val, prev) {
|
|
382
|
+
return prev.concat([val]);
|
|
383
|
+
}
|
|
193
384
|
program.parse(process.argv);
|
|
194
385
|
//# sourceMappingURL=runner.js.map
|
package/dist/core/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface APISnapOptions {
|
|
2
|
+
/** Routes to skip during health checks (e.g. ['/admin', '/internal']) */
|
|
3
|
+
skip?: string[];
|
|
4
|
+
/** Custom name shown in discovery response */
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const init: (app: any, options?: APISnapOptions) => void;
|
|
2
8
|
declare const _default: {
|
|
3
|
-
init: (app: any) => void;
|
|
9
|
+
init: (app: any, options?: APISnapOptions) => void;
|
|
4
10
|
};
|
|
5
11
|
export default _default;
|
|
6
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI,GAAI,KAAK,GAAG,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC3B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,IAAI,GAAI,KAAK,GAAG,EAAE,UAAS,cAAmB,SA6F1D,CAAC;;gBA7FwB,GAAG,YAAW,cAAc;;AA+FtD,wBAAwB"}
|
package/dist/middleware/index.js
CHANGED
|
@@ -1,43 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.init = void 0;
|
|
4
|
-
const init = (app) => {
|
|
5
|
-
const MASTER_KEY = 'apisnap_secret_handshake_2024';
|
|
4
|
+
const init = (app, options = {}) => {
|
|
6
5
|
const DISCOVERY_PATH = '/__apisnap_discovery';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const isLocal = req.hostname === 'localhost' || req.hostname === '127.0.0.1';
|
|
11
|
-
// If the key matches AND it's local, we bypass ALL other auth (Better Auth, etc.)
|
|
12
|
-
if (clientKey === MASTER_KEY && isLocal) {
|
|
13
|
-
// We "mock" a dev user so the routes think someone is logged in
|
|
14
|
-
req.user = { id: 'dev-bypass', role: 'admin', name: 'APISnap-Bot' };
|
|
15
|
-
return next();
|
|
16
|
-
}
|
|
17
|
-
next();
|
|
18
|
-
});
|
|
19
|
-
// Recursive function to find ALL routes, even in sub-routers
|
|
20
|
-
const splitRoutes = (stack, prefix = '') => {
|
|
6
|
+
const skipList = options.skip || [];
|
|
7
|
+
// ─── Recursive Route Extractor ────────────────────────────────────────────
|
|
8
|
+
const extractRoutes = (stack, prefix = '') => {
|
|
21
9
|
let routes = [];
|
|
22
10
|
stack.forEach((layer) => {
|
|
23
11
|
if (layer.route) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
12
|
+
const path = (prefix + layer.route.path).replace('//', '/');
|
|
13
|
+
// Skip the discovery endpoint itself and any user-skipped paths
|
|
14
|
+
if (path === DISCOVERY_PATH)
|
|
15
|
+
return;
|
|
16
|
+
if (skipList.some((s) => path.startsWith(s)))
|
|
17
|
+
return;
|
|
18
|
+
const methods = Object.keys(layer.route.methods)
|
|
19
|
+
.filter((m) => m !== '_all')
|
|
20
|
+
.map((m) => m.toUpperCase());
|
|
21
|
+
routes.push({ path, methods });
|
|
28
22
|
}
|
|
29
23
|
else if (layer.handle && layer.handle.stack) {
|
|
30
|
-
// Nested Router - GO DEEPER
|
|
31
|
-
// Extract the prefix from the regexp (e.g. /api or /api/community)
|
|
32
24
|
let rStr = layer.regexp.toString();
|
|
33
25
|
let routerPrefix = '';
|
|
34
|
-
// Try to match standard Express router regexp: /^\/api\/?(?=\/|$)/i
|
|
35
26
|
const standardMatch = rStr.match(/^\/\^\\\/(.*?)\\\/\?\(\?\=\\\/\|\$\)\/i$/);
|
|
36
27
|
if (standardMatch) {
|
|
37
28
|
routerPrefix = standardMatch[1];
|
|
38
29
|
}
|
|
39
30
|
else {
|
|
40
|
-
// Fallback for custom or older regexes
|
|
41
31
|
const fallbackMatch = rStr.match(/^\/\^\\?(.*?)\\?\/?(?:\(\?=\\\/\|\$\))?\//);
|
|
42
32
|
routerPrefix = fallbackMatch ? fallbackMatch[1] : '';
|
|
43
33
|
}
|
|
@@ -45,32 +35,31 @@ const init = (app) => {
|
|
|
45
35
|
if (routerPrefix && !routerPrefix.startsWith('/')) {
|
|
46
36
|
routerPrefix = '/' + routerPrefix;
|
|
47
37
|
}
|
|
48
|
-
// Avoid double slashes in concatenation
|
|
49
38
|
const newPrefix = (prefix + routerPrefix).replace(/\/\//g, '/');
|
|
50
|
-
routes = routes.concat(
|
|
39
|
+
routes = routes.concat(extractRoutes(layer.handle.stack, newPrefix));
|
|
51
40
|
}
|
|
52
41
|
});
|
|
53
42
|
return routes;
|
|
54
43
|
};
|
|
44
|
+
// ─── Discovery Endpoint ───────────────────────────────────────────────────
|
|
45
|
+
// IMPORTANT: This is registered FIRST so auth middleware added later won't
|
|
46
|
+
// wrap it. If your auth is global (app.use), see the bypass middleware below.
|
|
55
47
|
app.get(DISCOVERY_PATH, (req, res) => {
|
|
56
48
|
try {
|
|
57
|
-
// Safely get Express router (v4 uses _router, v5 uses router)
|
|
58
49
|
let router = app._router;
|
|
59
50
|
if (!router) {
|
|
60
51
|
try {
|
|
61
52
|
router = app.router;
|
|
62
53
|
}
|
|
63
|
-
catch (
|
|
64
|
-
// Ignore getter deprecation errors from Express 4
|
|
65
|
-
}
|
|
54
|
+
catch (_) { }
|
|
66
55
|
}
|
|
67
56
|
if (!router) {
|
|
68
|
-
res.status(500).json({ error: 'Router not initialized yet' });
|
|
69
|
-
return;
|
|
57
|
+
return res.status(500).json({ error: 'Router not initialized yet. Make sure apisnap.init(app) is called after your routes.' });
|
|
70
58
|
}
|
|
71
|
-
const allRoutes =
|
|
59
|
+
const allRoutes = extractRoutes(router.stack);
|
|
72
60
|
res.json({
|
|
73
|
-
|
|
61
|
+
tool: 'APISnap',
|
|
62
|
+
appName: options.name || 'Express App',
|
|
74
63
|
timestamp: new Date().toISOString(),
|
|
75
64
|
total: allRoutes.length,
|
|
76
65
|
endpoints: allRoutes,
|
|
@@ -80,7 +69,27 @@ const init = (app) => {
|
|
|
80
69
|
res.status(500).json({ error: 'Failed to parse routes', detail: e.message });
|
|
81
70
|
}
|
|
82
71
|
});
|
|
83
|
-
|
|
72
|
+
// ─── Auth Bypass Middleware ───────────────────────────────────────────────
|
|
73
|
+
// This intercepts requests to the discovery path and short-circuits any
|
|
74
|
+
// downstream auth middleware the user may have added globally.
|
|
75
|
+
// Works by monkey-patching app.use to detect auth-style middleware.
|
|
76
|
+
const originalUse = app.use.bind(app);
|
|
77
|
+
app.use = function (...args) {
|
|
78
|
+
// If it's a global middleware (no path), wrap it to skip discovery route
|
|
79
|
+
if (typeof args[0] === 'function') {
|
|
80
|
+
const originalMiddleware = args[0];
|
|
81
|
+
args[0] = (req, res, next) => {
|
|
82
|
+
if (req.path === DISCOVERY_PATH)
|
|
83
|
+
return next();
|
|
84
|
+
return originalMiddleware(req, res, next);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return originalUse(...args);
|
|
88
|
+
};
|
|
89
|
+
console.log(`\x1b[32m✅ [APISnap] Discovery active → http://localhost:PORT${DISCOVERY_PATH}\x1b[0m`);
|
|
90
|
+
if (skipList.length > 0) {
|
|
91
|
+
console.log(`\x1b[33m⏭ [APISnap] Skipping: ${skipList.join(', ')}\x1b[0m`);
|
|
92
|
+
}
|
|
84
93
|
};
|
|
85
94
|
exports.init = init;
|
|
86
95
|
exports.default = { init: exports.init };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@umeshindu222/apisnap",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Instant API auto-discovery and health-check CLI for Express.js",
|
|
5
|
-
"main": "dist/middleware/index.js",
|
|
6
|
-
"types": "dist/middleware/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"apisnap": "bin/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"bin",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
17
|
-
"prepublishOnly": "npm run build"
|
|
18
|
-
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"express",
|
|
21
|
-
"api",
|
|
22
|
-
"testing",
|
|
23
|
-
"health-check",
|
|
24
|
-
"cli",
|
|
25
|
-
"automation"
|
|
26
|
-
],
|
|
27
|
-
"author": "Umesh Induranga",
|
|
28
|
-
"license": "MIT",
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"axios": "^1.13.6",
|
|
31
|
-
"chalk": "^5.6.2",
|
|
32
|
-
"commander": "^14.0.3",
|
|
33
|
-
"express": "^4.22.1",
|
|
34
|
-
"ora": "^9.3.0"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@types/express": "^5.0.6",
|
|
38
|
-
"@types/node": "^25.3.3",
|
|
39
|
-
"ts-node": "^10.9.2",
|
|
40
|
-
"typescript": "^5.9.3"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@umeshindu222/apisnap",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"description": "Instant API auto-discovery and health-check CLI for Express.js",
|
|
5
|
+
"main": "dist/middleware/index.js",
|
|
6
|
+
"types": "dist/middleware/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"apisnap": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"express",
|
|
21
|
+
"api",
|
|
22
|
+
"testing",
|
|
23
|
+
"health-check",
|
|
24
|
+
"cli",
|
|
25
|
+
"automation"
|
|
26
|
+
],
|
|
27
|
+
"author": "Umesh Induranga",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"axios": "^1.13.6",
|
|
31
|
+
"chalk": "^5.6.2",
|
|
32
|
+
"commander": "^14.0.3",
|
|
33
|
+
"express": "^4.22.1",
|
|
34
|
+
"ora": "^9.3.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/express": "^5.0.6",
|
|
38
|
+
"@types/node": "^25.3.3",
|
|
39
|
+
"ts-node": "^10.9.2",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|