@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 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
+ [![npm version](https://img.shields.io/npm/v/apisnap.svg)](https://www.npmjs.com/package/apisnap)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This tells the OS to run this file with Node.js
4
+ // It points to your compiled TypeScript code
5
+ require('../dist/core/runner.js');
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runner.d.ts.map
@@ -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,2 @@
1
+ export declare const init: (app: any) => void;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }