@umeshindu222/apisnap 1.0.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/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/cli.js +5 -0
- package/dist/core/runner.d.ts +2 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +159 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +53 -0
- package/dist/middleware/index.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 umesh induranga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# ๐ธ APISnap
|
|
2
|
+
|
|
3
|
+
> Instant API auto-discovery and health-check CLI for Express.js
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/apisnap)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ๐ค Why APISnap?
|
|
11
|
+
|
|
12
|
+
**The Problem:** Every time you make a change to your Express.js backend, you have to manually open Postman, find every route, and click "Send" one by one. For a project with 20+ endpoints, this is slow, error-prone, and boring.
|
|
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.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## โจ Features
|
|
19
|
+
|
|
20
|
+
- ๐ **Auto Route Discovery** โ Scans your Express router stack, no manual config needed
|
|
21
|
+
- ๐ **Auth Header Support** โ Pass JWT tokens or API keys via `--header`
|
|
22
|
+
- โ ๏ธ **Slow Route Detection** โ Flags endpoints that exceed your response time threshold
|
|
23
|
+
- ๐พ **JSON Report Export** โ Save results to a file for CI/CD pipelines or sharing
|
|
24
|
+
- ๐จ **Beautiful CLI Output** โ Color-coded results with spinners and summary table
|
|
25
|
+
- ๐ **Express v4 & v5** โ Compatible with both versions
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## ๐ Quick Start
|
|
30
|
+
|
|
31
|
+
### Step 1: Add the middleware to your Express app
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const express = require('express');
|
|
35
|
+
const apisnap = require('apisnap');
|
|
36
|
+
|
|
37
|
+
const app = express();
|
|
38
|
+
|
|
39
|
+
// Your routes here
|
|
40
|
+
app.get('/users', (req, res) => res.json({ users: [] }));
|
|
41
|
+
app.post('/users', (req, res) => res.json({ message: 'Created' }));
|
|
42
|
+
|
|
43
|
+
// Add APISnap โ place AFTER your routes
|
|
44
|
+
apisnap.init(app);
|
|
45
|
+
|
|
46
|
+
app.listen(3000);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Step 2: Run the health check
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx apisnap --port 3000
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That's it. APISnap finds and tests every route automatically.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## ๐ CLI Usage
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx apisnap [options]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Option | Description | Default |
|
|
66
|
+
|--------|-------------|---------|
|
|
67
|
+
| `-p, --port <number>` | Port your server is running on | `3000` |
|
|
68
|
+
| `-H, --header <string>` | Custom header for auth (e.g., `"Authorization: Bearer token"`) | โ |
|
|
69
|
+
| `-s, --slow <number>` | Response time threshold in ms for slow warnings | `200` |
|
|
70
|
+
| `-e, --export <filename>` | Export results to a JSON file | โ |
|
|
71
|
+
| `-V, --version` | Show version number | โ |
|
|
72
|
+
| `-h, --help` | Show help | โ |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## ๐งช Examples
|
|
77
|
+
|
|
78
|
+
### Basic health check
|
|
79
|
+
```bash
|
|
80
|
+
npx apisnap --port 3000
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### With authentication
|
|
84
|
+
```bash
|
|
85
|
+
npx apisnap --port 3000 --header "Authorization: Bearer eyJhbGci..."
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom slow threshold (flag routes > 500ms)
|
|
89
|
+
```bash
|
|
90
|
+
npx apisnap --port 3000 --slow 500
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Export report to JSON
|
|
94
|
+
```bash
|
|
95
|
+
npx apisnap --port 3000 --export my-report
|
|
96
|
+
# Creates: my-report.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### All options together
|
|
100
|
+
```bash
|
|
101
|
+
npx apisnap --port 5000 --header "Authorization: Bearer TOKEN" --slow 300 --export ci-report
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## ๐ Sample Output
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
๐ธ APISnap v1.0.0
|
|
110
|
+
Slow threshold: 200ms
|
|
111
|
+
|
|
112
|
+
โ Connected! Found 6 endpoints.
|
|
113
|
+
|
|
114
|
+
โ GET /health [200 OK] 3ms
|
|
115
|
+
โ GET /users [200 OK] 12ms
|
|
116
|
+
โ POST /users [200 OK] 8ms
|
|
117
|
+
โ GET /users/1 [200 OK] 15ms
|
|
118
|
+
โ ๏ธ GET /reports [200 OK] 543ms โ slow!
|
|
119
|
+
โ DELETE /users/1 [401]
|
|
120
|
+
|
|
121
|
+
๐ Summary:
|
|
122
|
+
โ
Passed: 5
|
|
123
|
+
โ Failed: 1
|
|
124
|
+
โ ๏ธ Slow: 1 (>200ms)
|
|
125
|
+
|
|
126
|
+
โ ๏ธ Some endpoints are unhealthy!
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## ๐พ JSON Report Format
|
|
132
|
+
|
|
133
|
+
When using `--export`, a structured JSON file is created:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"tool": "APISnap",
|
|
138
|
+
"generatedAt": "2026-03-06T15:56:20.375Z",
|
|
139
|
+
"config": { "port": "3000", "slowThreshold": 200 },
|
|
140
|
+
"summary": { "total": 6, "passed": 5, "failed": 1, "slow": 1 },
|
|
141
|
+
"results": [
|
|
142
|
+
{ "method": "GET", "path": "/health", "status": 200, "duration": 3, "success": true, "slow": false },
|
|
143
|
+
{ "method": "GET", "path": "/users", "status": 200, "duration": 12, "success": true, "slow": false }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **Pro tip:** Use this in CI/CD pipelines โ parse `summary.failed` and fail the build if it's greater than 0!
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## ๐ง How It Works
|
|
153
|
+
|
|
154
|
+
APISnap uses a two-part architecture:
|
|
155
|
+
|
|
156
|
+
1. **Middleware (The Seeker)** โ `apisnap.init(app)` injects a hidden endpoint `/__apisnap_discovery` into your Express app. When called, it recursively walks the Express router stack and returns a map of every registered route.
|
|
157
|
+
|
|
158
|
+
2. **CLI Runner (The Checker)** โ `npx apisnap` calls the discovery endpoint, gets the route map, then "pings" each route using axios โ injecting your headers, replacing path params with defaults (`:id` โ `1`), and timing each response.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## ๐ค Contributing
|
|
163
|
+
|
|
164
|
+
Contributions, issues and feature requests are welcome!
|
|
165
|
+
|
|
166
|
+
1. Fork the repo
|
|
167
|
+
2. Create your feature branch: `git checkout -b feat/amazing-feature`
|
|
168
|
+
3. Commit your changes: `git commit -m 'feat: add amazing feature'`
|
|
169
|
+
4. Push to the branch: `git push origin feat/amazing-feature`
|
|
170
|
+
5. Open a Pull Request
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## ๐ License
|
|
175
|
+
|
|
176
|
+
MIT ยฉ [Umesh Induranga](https://github.com/Umeshinduranga)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('apisnap')
|
|
14
|
+
.description('Instant API health-check CLI for Express.js')
|
|
15
|
+
.version('1.0.0')
|
|
16
|
+
.option('-p, --port <number>', 'The port your server is running on', '3000')
|
|
17
|
+
.option('-H, --header <string>', 'Add a custom header (e.g., "Authorization: Bearer token")')
|
|
18
|
+
.option('-s, --slow <number>', 'Threshold for slow response warning (ms)', '200')
|
|
19
|
+
.option('-e, --export <filename>', 'Export results to a JSON file (e.g., report.json)')
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const port = options.port;
|
|
22
|
+
const slowThreshold = parseInt(options.slow);
|
|
23
|
+
const discoveryUrl = `http://localhost:${port}/__apisnap_discovery`;
|
|
24
|
+
const results = []; // Collect all test results here
|
|
25
|
+
// Parse custom header from CLI flag
|
|
26
|
+
const customHeaders = {};
|
|
27
|
+
if (options.header) {
|
|
28
|
+
const [key, ...value] = options.header.split(':');
|
|
29
|
+
if (key && value) {
|
|
30
|
+
customHeaders[key.trim()] = value.join(':').trim();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk_1.default.bold.cyan(`\n๐ธ APISnap v1.0.0`));
|
|
34
|
+
// Show active options
|
|
35
|
+
if (Object.keys(customHeaders).length > 0) {
|
|
36
|
+
console.log(chalk_1.default.gray(` Headers: ${JSON.stringify(customHeaders)}`));
|
|
37
|
+
}
|
|
38
|
+
console.log(chalk_1.default.gray(` Slow threshold: ${slowThreshold}ms`));
|
|
39
|
+
if (options.export) {
|
|
40
|
+
console.log(chalk_1.default.gray(` Export: ${options.export}`));
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
const spinner = (0, ora_1.default)('Connecting to your API...').start();
|
|
44
|
+
try {
|
|
45
|
+
// 1. Fetch the route map from the middleware
|
|
46
|
+
const response = await axios_1.default.get(discoveryUrl);
|
|
47
|
+
const { endpoints } = response.data;
|
|
48
|
+
spinner.succeed(chalk_1.default.green(`Connected! Found ${endpoints.length} endpoints.\n`));
|
|
49
|
+
// Summary counters
|
|
50
|
+
let passed = 0;
|
|
51
|
+
let failed = 0;
|
|
52
|
+
let slow = 0;
|
|
53
|
+
// 2. Loop through each discovered endpoint
|
|
54
|
+
for (const endpoint of endpoints) {
|
|
55
|
+
const method = endpoint.methods[0];
|
|
56
|
+
let path = endpoint.path;
|
|
57
|
+
// Smart Parameter Replacement โ :id, :slug โ 1
|
|
58
|
+
if (path.includes(':')) {
|
|
59
|
+
path = path.replace(/:[a-zA-Z0-9]+/g, '1');
|
|
60
|
+
}
|
|
61
|
+
const fullUrl = `http://localhost:${port}${path}`;
|
|
62
|
+
const testSpinner = (0, ora_1.default)(`Testing ${chalk_1.default.bold(method)} ${path}`).start();
|
|
63
|
+
// Step 8: Initialize result object for this endpoint
|
|
64
|
+
const testResult = {
|
|
65
|
+
method,
|
|
66
|
+
path,
|
|
67
|
+
fullUrl,
|
|
68
|
+
status: 0,
|
|
69
|
+
duration: 0,
|
|
70
|
+
success: false,
|
|
71
|
+
slow: false,
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
const res = await (0, axios_1.default)({
|
|
76
|
+
method: method,
|
|
77
|
+
url: fullUrl,
|
|
78
|
+
headers: {
|
|
79
|
+
...customHeaders,
|
|
80
|
+
'User-Agent': 'APISnap/1.0.0',
|
|
81
|
+
},
|
|
82
|
+
timeout: 5000,
|
|
83
|
+
});
|
|
84
|
+
const duration = Date.now() - startTime;
|
|
85
|
+
// Step 8: Populate result
|
|
86
|
+
testResult.duration = duration;
|
|
87
|
+
testResult.status = res.status;
|
|
88
|
+
testResult.success = true;
|
|
89
|
+
// Step 7: Performance threshold check
|
|
90
|
+
let statusIcon = chalk_1.default.green('โ');
|
|
91
|
+
let durationColor = chalk_1.default.gray;
|
|
92
|
+
if (duration > slowThreshold) {
|
|
93
|
+
statusIcon = chalk_1.default.yellow('โ ๏ธ ');
|
|
94
|
+
durationColor = chalk_1.default.yellow.bold;
|
|
95
|
+
testResult.slow = true;
|
|
96
|
+
slow++;
|
|
97
|
+
}
|
|
98
|
+
testSpinner.succeed(`${statusIcon} ${chalk_1.default.bold(method)} ${chalk_1.default.white(path)} ` +
|
|
99
|
+
`${chalk_1.default.green(`[${res.status} OK]`)} ` +
|
|
100
|
+
`${durationColor(`${duration}ms`)}`);
|
|
101
|
+
passed++;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
testResult.status = err.response?.status || 500;
|
|
105
|
+
testResult.success = false;
|
|
106
|
+
const status = err.response?.status || 'FAIL';
|
|
107
|
+
testSpinner.fail(`${chalk_1.default.bold(method)} ${chalk_1.default.white(path)} ` +
|
|
108
|
+
`${chalk_1.default.red(`[${status}]`)}`);
|
|
109
|
+
failed++;
|
|
110
|
+
}
|
|
111
|
+
results.push(testResult); // Step 8: Save result to list
|
|
112
|
+
}
|
|
113
|
+
// Summary Statistics
|
|
114
|
+
console.log(chalk_1.default.bold('\n๐ Summary:'));
|
|
115
|
+
console.log(chalk_1.default.green(` โ
Passed: ${passed}`));
|
|
116
|
+
console.log(chalk_1.default.red(` โ Failed: ${failed}`));
|
|
117
|
+
console.log(chalk_1.default.yellow(` โ ๏ธ Slow: ${slow} (>${slowThreshold}ms)`));
|
|
118
|
+
if (failed > 0) {
|
|
119
|
+
console.log(chalk_1.default.red.bold('\nโ ๏ธ Some endpoints are unhealthy!'));
|
|
120
|
+
}
|
|
121
|
+
else if (slow > 0) {
|
|
122
|
+
console.log(chalk_1.default.yellow.bold('\n๐ข All endpoints alive, but some are slow!'));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(chalk_1.default.green.bold('\nโจ All systems nominal!'));
|
|
126
|
+
}
|
|
127
|
+
// Step 8: Export report to JSON file
|
|
128
|
+
if (options.export) {
|
|
129
|
+
const filePath = options.export.endsWith('.json')
|
|
130
|
+
? options.export
|
|
131
|
+
: `${options.export}.json`;
|
|
132
|
+
const reportData = {
|
|
133
|
+
tool: 'APISnap',
|
|
134
|
+
generatedAt: new Date().toISOString(),
|
|
135
|
+
config: {
|
|
136
|
+
port,
|
|
137
|
+
slowThreshold,
|
|
138
|
+
headers: customHeaders,
|
|
139
|
+
},
|
|
140
|
+
summary: {
|
|
141
|
+
total: endpoints.length,
|
|
142
|
+
passed,
|
|
143
|
+
failed,
|
|
144
|
+
slow,
|
|
145
|
+
},
|
|
146
|
+
results,
|
|
147
|
+
};
|
|
148
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(reportData, null, 2));
|
|
149
|
+
console.log(chalk_1.default.cyan.bold(`\n๐พ Report saved to: ${chalk_1.default.white(filePath)}`));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
spinner.fail(chalk_1.default.red(`Failed to connect to ${discoveryUrl}`));
|
|
154
|
+
console.log(chalk_1.default.yellow('Is your server running? Make sure apisnap.init(app) is added.\n'));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
program.parse(process.argv);
|
|
159
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,IAAI,GAAI,KAAK,GAAG,SAwD5B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = void 0;
|
|
4
|
+
const init = (app) => {
|
|
5
|
+
const DISCOVERY_PATH = '/__apisnap_discovery';
|
|
6
|
+
// Recursive function to find ALL routes, even in sub-routers
|
|
7
|
+
const splitRoutes = (stack, prefix = '') => {
|
|
8
|
+
let routes = [];
|
|
9
|
+
stack.forEach((layer) => {
|
|
10
|
+
if (layer.route) {
|
|
11
|
+
// Simple direct route
|
|
12
|
+
const path = prefix + layer.route.path;
|
|
13
|
+
const methods = Object.keys(layer.route.methods).map((m) => m.toUpperCase());
|
|
14
|
+
routes.push({ path: path.replace('//', '/'), methods });
|
|
15
|
+
}
|
|
16
|
+
else if (layer.handle && layer.handle.stack) {
|
|
17
|
+
// Nested Router - GO DEEPER
|
|
18
|
+
// Extract the prefix from the regexp (e.g. /api)
|
|
19
|
+
const match = layer.regexp
|
|
20
|
+
.toString()
|
|
21
|
+
.match(/^\/\^\\?(.*?)\\?\/?(?:\(\?=\\\/\|\$\))?\//);
|
|
22
|
+
const routerPrefix = match
|
|
23
|
+
? match[1].replace(/\\\//g, '/')
|
|
24
|
+
: '';
|
|
25
|
+
routes = routes.concat(splitRoutes(layer.handle.stack, prefix + '/' + routerPrefix));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return routes;
|
|
29
|
+
};
|
|
30
|
+
app.get(DISCOVERY_PATH, (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
// Express v5 uses app.router, Express v4 uses app._router
|
|
33
|
+
const router = app.router || app._router;
|
|
34
|
+
if (!router) {
|
|
35
|
+
res.status(500).json({ error: 'Router not initialized yet' });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const allRoutes = splitRoutes(router.stack);
|
|
39
|
+
res.json({
|
|
40
|
+
name: 'APISnap Discovery',
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
total: allRoutes.length,
|
|
43
|
+
endpoints: allRoutes,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
res.status(500).json({ error: 'Failed to parse routes', detail: e.message });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
console.log(`\x1b[32m%s\x1b[0m`, `โ
[APISnap] Discovery active at ${DISCOVERY_PATH}`);
|
|
51
|
+
};
|
|
52
|
+
exports.init = init;
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":";;;AAEO,MAAM,IAAI,GAAG,CAAC,GAAQ,EAAE,EAAE;IAC7B,MAAM,cAAc,GAAG,sBAAsB,CAAC;IAE9C,6DAA6D;IAC7D,MAAM,WAAW,GAAG,CAAC,KAAY,EAAE,MAAM,GAAG,EAAE,EAAS,EAAE;QACrD,IAAI,MAAM,GAAU,EAAE,CAAC;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,sBAAsB;gBACtB,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;gBACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACvD,CAAC,CAAC,WAAW,EAAE,CAClB,CAAC;gBACF,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC5C,4BAA4B;gBAC5B,iDAAiD;gBACjD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM;qBACrB,QAAQ,EAAE;qBACV,KAAK,CAAC,2CAA2C,CAAC,CAAC;gBACxD,MAAM,YAAY,GAAG,KAAK;oBACtB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;oBAChC,CAAC,CAAC,EAAE,CAAC;gBACT,MAAM,GAAG,MAAM,CAAC,MAAM,CAClB,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,YAAY,CAAC,CAC/D,CAAC;YACN,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAClB,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACpD,IAAI,CAAC;YACD,0DAA0D;YAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC;YACzC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAC;gBAC9D,OAAO;YACX,CAAC;YACD,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC;gBACL,IAAI,EAAE,mBAAmB;gBACzB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,KAAK,EAAE,SAAS,CAAC,MAAM;gBACvB,SAAS,EAAE,SAAS;aACvB,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACjF,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CACP,mBAAmB,EACnB,mCAAmC,cAAc,EAAE,CACtD,CAAC;AACN,CAAC,CAAC;AAxDW,QAAA,IAAI,QAwDf"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umeshindu222/apisnap",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"ora": "^9.3.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/express": "^5.0.6",
|
|
37
|
+
"@types/node": "^25.3.3",
|
|
38
|
+
"ts-node": "^10.9.2",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
}
|
|
41
|
+
}
|