@lowdep/req-test 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 Rushabh Shah
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
+ # req-test
2
+
3
+ ![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen) ![Node](https://img.shields.io/badge/node-%3E%3D14-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
4
+
5
+
6
+ File-based HTTP test runner. Define your API tests in a `requests.json` file and run them like a test suite. Zero dependencies.
7
+
8
+ Like Postman/Newman, but a single Node.js file you can commit to your repo.
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g req-test
16
+ ```
17
+
18
+ Or without installing:
19
+
20
+ ```bash
21
+ npx req-test
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ```bash
29
+ # Create a starter requests.json
30
+ req-test init
31
+
32
+ # Run the tests
33
+ req-test
34
+
35
+ # Run a specific file
36
+ req-test ./tests/api.json
37
+
38
+ # Verbose output (show all assertions)
39
+ req-test --verbose
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Test File Format (`requests.json`)
45
+
46
+ ```json
47
+ {
48
+ "vars": {
49
+ "base": "http://localhost:3000",
50
+ "token": "my-auth-token"
51
+ },
52
+ "timeout": 5000,
53
+ "tests": [
54
+ {
55
+ "name": "Health check",
56
+ "method": "GET",
57
+ "url": "{{base}}/health",
58
+ "expect": {
59
+ "status": 200,
60
+ "json": { "status": "ok" },
61
+ "maxLatency": 200
62
+ }
63
+ },
64
+ {
65
+ "name": "Login and get token",
66
+ "method": "POST",
67
+ "url": "{{base}}/auth/login",
68
+ "body": { "email": "test@example.com", "password": "secret" },
69
+ "expect": { "status": 200 },
70
+ "extract": { "authToken": "$.token" }
71
+ },
72
+ {
73
+ "name": "Get protected resource",
74
+ "method": "GET",
75
+ "url": "{{base}}/api/profile",
76
+ "headers": { "Authorization": "Bearer {{authToken}}" },
77
+ "expect": {
78
+ "status": 200,
79
+ "jsonPath": { "$.email": "test@example.com" }
80
+ }
81
+ },
82
+ {
83
+ "name": "Unauthenticated request is rejected",
84
+ "method": "GET",
85
+ "url": "{{base}}/api/profile",
86
+ "expect": { "statusRange": "4xx" },
87
+ "skip": false
88
+ }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Example Output
96
+
97
+ ```
98
+ req-test requests.json
99
+ 4 test(s) · base: http://localhost:3000
100
+
101
+ ✓ Health check 200 · 12ms
102
+ ✓ Login and get token 200 · 45ms
103
+ ✓ Get protected resource 200 · 18ms
104
+ ✓ Unauthenticated request is rejected 401 · 8ms
105
+
106
+ 4 passed of 4 tests
107
+ ```
108
+
109
+ **With failures:**
110
+ ```
111
+ ✘ Get protected resource 500 · 203ms
112
+ ✘ status 500 != 200
113
+ ✘ latency 203ms > 200ms
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Assertions
119
+
120
+ | Assertion | Type | Description |
121
+ |---|---|---|
122
+ | `status` | `number` | Exact HTTP status code |
123
+ | `statusRange` | `"2xx"` \| `"4xx"` \| `"5xx"` | Status code class |
124
+ | `json` | `object` | Partial JSON match (nested ok) |
125
+ | `jsonPath` | `object` | `{ "$.user.name": "Alice" }` |
126
+ | `bodyContains` | `string` | String present in response body |
127
+ | `bodyNotContains` | `string` | String absent from response body |
128
+ | `header` | `object` | `{ "content-type": "application/json" }` |
129
+ | `maxLatency` | `number` | Max response time in milliseconds |
130
+
131
+ ---
132
+
133
+ ## Variables & Chaining
134
+
135
+ Use `{{varName}}` in any string field. Define `vars` at suite level, or extract from responses with `extract`:
136
+
137
+ ```json
138
+ {
139
+ "extract": { "userId": "$.data.id" }
140
+ }
141
+ ```
142
+
143
+ Extracted values are available in all subsequent tests as `{{userId}}`.
144
+
145
+ ---
146
+
147
+ ## CI Integration
148
+
149
+ ```yaml
150
+ - name: API Tests
151
+ run: npx req-test ./tests/integration.json
152
+ ```
153
+
154
+ Exit code is `1` if any test fails.
155
+
156
+ ---
157
+
158
+ ## License
159
+
160
+ MIT
161
+
162
+ ---
163
+
164
+ ## Keywords
165
+
166
+ `api testing` · `http test runner` · `postman alternative` · `newman alternative` · `rest client` · `api assertions` · `integration test` · `curl test` · `zero dependencies` · `ci`
167
+
168
+ ---
169
+
170
+ <div align="center">
171
+
172
+ **Built to solve, shared to help — Rushabh Shah 🛠️✨**
173
+
174
+ <sub>One of 40+ zero-dependency developer CLI tools — no <code>node_modules</code>, ever.</sub>
175
+
176
+ </div>
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const http = require('http');
5
+ const https = require('https');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const url = require('url');
9
+
10
+ const VERSION = '1.0.0';
11
+ const CFG_FILE = 'requests.json';
12
+
13
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
14
+ const isTTY = process.stdout.isTTY;
15
+ const c = (code, t) => isTTY ? `\x1b[${code}m${t}\x1b[0m` : t;
16
+ const bold = t => c('1', t);
17
+ const dim = t => c('2', t);
18
+ const red = t => c('31', t);
19
+ const green = t => c('32', t);
20
+ const yellow = t => c('33', t);
21
+ const cyan = t => c('36', t);
22
+
23
+ // ─── HTTP fetch ───────────────────────────────────────────────────────────────
24
+ function fetchRequest(opts) {
25
+ return new Promise((resolve, reject) => {
26
+ const { method = 'GET', url: rawUrl, headers = {}, body, timeout = 10000 } = opts;
27
+ const parsed = new url.URL(rawUrl);
28
+ const lib = parsed.protocol === 'https:' ? https : http;
29
+
30
+ const reqOpts = {
31
+ hostname: parsed.hostname,
32
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
33
+ path: parsed.pathname + parsed.search,
34
+ method: method.toUpperCase(),
35
+ headers: { 'User-Agent': `req-test/${VERSION}`, ...headers },
36
+ };
37
+
38
+ let bodyStr = '';
39
+ if (body) {
40
+ bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
41
+ if (!reqOpts.headers['Content-Type']) reqOpts.headers['Content-Type'] = 'application/json';
42
+ reqOpts.headers['Content-Length'] = Buffer.byteLength(bodyStr);
43
+ }
44
+
45
+ const start = Date.now();
46
+ const req = lib.request(reqOpts, res => {
47
+ let raw = '';
48
+ res.setEncoding('utf8');
49
+ res.on('data', d => { raw += d; });
50
+ res.on('end', () => {
51
+ let json = null;
52
+ try { json = JSON.parse(raw); } catch {}
53
+ resolve({ status: res.statusCode, headers: res.headers, body: raw, json, latency: Date.now() - start });
54
+ });
55
+ });
56
+
57
+ req.setTimeout(timeout, () => { req.destroy(); reject(new Error(`Timeout after ${timeout}ms`)); });
58
+ req.on('error', reject);
59
+ if (bodyStr) req.write(bodyStr);
60
+ req.end();
61
+ });
62
+ }
63
+
64
+ // ─── Assertions ───────────────────────────────────────────────────────────────
65
+ // Returns array of { pass, message }
66
+ function runAssertions(expect, response) {
67
+ const results = [];
68
+
69
+ // Status
70
+ if (expect.status !== undefined) {
71
+ const pass = response.status === expect.status;
72
+ results.push({ pass, message: `status ${response.status} ${pass ? '==' : '!='} ${expect.status}` });
73
+ }
74
+
75
+ // Status range: "2xx", "4xx"
76
+ if (expect.statusRange) {
77
+ const code = String(response.status)[0];
78
+ const range = String(expect.statusRange)[0];
79
+ const pass = code === range;
80
+ results.push({ pass, message: `status ${response.status} ${pass ? 'is' : 'is not'} ${expect.statusRange}` });
81
+ }
82
+
83
+ // Body contains string
84
+ if (expect.bodyContains !== undefined) {
85
+ const pass = response.body.includes(String(expect.bodyContains));
86
+ results.push({ pass, message: `body ${pass ? 'contains' : 'does not contain'} "${expect.bodyContains}"` });
87
+ }
88
+
89
+ // Body does not contain
90
+ if (expect.bodyNotContains !== undefined) {
91
+ const pass = !response.body.includes(String(expect.bodyNotContains));
92
+ results.push({ pass, message: `body ${pass ? 'does not contain' : 'contains'} "${expect.bodyNotContains}"` });
93
+ }
94
+
95
+ // JSON deep partial match
96
+ if (expect.json !== undefined) {
97
+ const { pass, msg } = jsonPartialMatch(response.json, expect.json, '');
98
+ results.push({ pass, message: msg });
99
+ }
100
+
101
+ // JSON path assertions: { "$.user.name": "Alice" }
102
+ if (expect.jsonPath) {
103
+ for (const [jsonPath, expected] of Object.entries(expect.jsonPath)) {
104
+ const actual = getJsonPath(response.json, jsonPath);
105
+ const pass = deepEqual(actual, expected);
106
+ results.push({ pass, message: `${jsonPath}: ${JSON.stringify(actual)} ${pass ? '==' : '!='} ${JSON.stringify(expected)}` });
107
+ }
108
+ }
109
+
110
+ // Response header
111
+ if (expect.header) {
112
+ for (const [key, val] of Object.entries(expect.header)) {
113
+ const actual = response.headers[key.toLowerCase()] || '';
114
+ const pass = val instanceof RegExp ? val.test(actual) : actual.includes(String(val));
115
+ results.push({ pass, message: `header "${key}": "${actual}" ${pass ? 'matches' : 'does not match'} "${val}"` });
116
+ }
117
+ }
118
+
119
+ // Max latency
120
+ if (expect.maxLatency !== undefined) {
121
+ const pass = response.latency <= expect.maxLatency;
122
+ results.push({ pass, message: `latency ${response.latency}ms ${pass ? '<=' : '>'} ${expect.maxLatency}ms` });
123
+ }
124
+
125
+ return results;
126
+ }
127
+
128
+ function jsonPartialMatch(actual, expected, path) {
129
+ if (expected === null || expected === undefined) {
130
+ return { pass: actual === expected, msg: `${path || 'root'}: ${JSON.stringify(actual)} == ${JSON.stringify(expected)}` };
131
+ }
132
+ if (typeof expected !== 'object' || Array.isArray(expected)) {
133
+ const pass = deepEqual(actual, expected);
134
+ return { pass, msg: `${path || 'root'}: ${JSON.stringify(actual)} ${pass ? '==' : '!='} ${JSON.stringify(expected)}` };
135
+ }
136
+ if (typeof actual !== 'object' || actual === null) {
137
+ return { pass: false, msg: `${path || 'root'}: expected object, got ${JSON.stringify(actual)}` };
138
+ }
139
+ for (const [k, v] of Object.entries(expected)) {
140
+ const subPath = path ? `${path}.${k}` : k;
141
+ const result = jsonPartialMatch(actual[k], v, subPath);
142
+ if (!result.pass) return result;
143
+ }
144
+ return { pass: true, msg: `json structure matches` };
145
+ }
146
+
147
+ function getJsonPath(obj, jsonPath) {
148
+ const keys = jsonPath.replace(/^\$\.?/, '').split(/[.\[\]]+/).filter(Boolean);
149
+ let current = obj;
150
+ for (const key of keys) {
151
+ if (current == null) return undefined;
152
+ current = current[key];
153
+ }
154
+ return current;
155
+ }
156
+
157
+ function deepEqual(a, b) {
158
+ if (a === b) return true;
159
+ if (typeof a !== typeof b) return false;
160
+ if (typeof a !== 'object') return false;
161
+ if (a === null || b === null) return false;
162
+ const ka = Object.keys(a), kb = Object.keys(b);
163
+ if (ka.length !== kb.length) return false;
164
+ return ka.every(k => deepEqual(a[k], b[k]));
165
+ }
166
+
167
+ // ─── Variable substitution ────────────────────────────────────────────────────
168
+ function substitute(value, vars) {
169
+ if (typeof value === 'string') {
170
+ return value.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
171
+ }
172
+ if (typeof value === 'object' && value !== null) {
173
+ if (Array.isArray(value)) return value.map(v => substitute(v, vars));
174
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, substitute(v, vars)]));
175
+ }
176
+ return value;
177
+ }
178
+
179
+ // ─── Test runner ──────────────────────────────────────────────────────────────
180
+ async function runSuite(suite, vars = {}) {
181
+ const results = [];
182
+ const allVars = { ...vars };
183
+
184
+ for (const test of suite.tests) {
185
+ if (test.skip) {
186
+ results.push({ name: test.name, skipped: true });
187
+ continue;
188
+ }
189
+
190
+ const t = substitute(test, allVars);
191
+ let response;
192
+
193
+ try {
194
+ response = await fetchRequest({
195
+ method: t.method || 'GET',
196
+ url: t.url,
197
+ headers: t.headers || {},
198
+ body: t.body,
199
+ timeout: t.timeout || suite.timeout || 10000,
200
+ });
201
+ } catch (e) {
202
+ results.push({ name: test.name, error: e.message, assertions: [] });
203
+ continue;
204
+ }
205
+
206
+ const assertions = t.expect ? runAssertions(t.expect, response) : [];
207
+
208
+ // Extract variables from response for use in later tests
209
+ if (t.extract) {
210
+ for (const [varName, jsonPath] of Object.entries(t.extract)) {
211
+ const extracted = getJsonPath(response.json, jsonPath);
212
+ if (extracted !== undefined) allVars[varName] = extracted;
213
+ }
214
+ }
215
+
216
+ results.push({
217
+ name: test.name,
218
+ status: response.status,
219
+ latency: response.latency,
220
+ assertions,
221
+ passed: assertions.every(a => a.pass),
222
+ error: null,
223
+ });
224
+ }
225
+
226
+ return results;
227
+ }
228
+
229
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
230
+ const args = process.argv.slice(2);
231
+ const flags = new Set(args.filter(a => a.startsWith('-')));
232
+ const positional = args.filter(a => !a.startsWith('-'));
233
+
234
+ if (flags.has('--version') || flags.has('-v')) {
235
+ console.log(`req-test v${VERSION}`); process.exit(0);
236
+ }
237
+
238
+ if (flags.has('--help') || flags.has('-h')) {
239
+ console.log(`
240
+ ${bold('req-test')} — File-based HTTP test runner
241
+
242
+ ${bold('USAGE')}
243
+ req-test [file] Run tests from a file (default: requests.json)
244
+ req-test init Create a starter requests.json
245
+
246
+ ${bold('OPTIONS')}
247
+ --verbose Show all assertions, not just failures
248
+ --version Show version
249
+ --help Show this help
250
+
251
+ ${bold('TEST FILE FORMAT')} (requests.json)
252
+ {
253
+ "vars": { "base": "http://localhost:3000" },
254
+ "timeout": 5000,
255
+ "tests": [
256
+ {
257
+ "name": "Get user",
258
+ "method": "GET",
259
+ "url": "{{base}}/api/users/1",
260
+ "expect": {
261
+ "status": 200,
262
+ "json": { "id": 1 },
263
+ "maxLatency": 500
264
+ },
265
+ "extract": { "userId": "$.id" }
266
+ }
267
+ ]
268
+ }
269
+
270
+ ${bold('ASSERTIONS')}
271
+ status Exact HTTP status code
272
+ statusRange Status class: "2xx", "4xx", "5xx"
273
+ json Partial JSON match (nested keys)
274
+ jsonPath JSONPath assertions: { "$.user.name": "Alice" }
275
+ bodyContains String present in response body
276
+ header Response header match: { "content-type": "application/json" }
277
+ maxLatency Maximum allowed response time in ms
278
+ `);
279
+ process.exit(0);
280
+ }
281
+
282
+ // init sub-command
283
+ if (positional[0] === 'init') {
284
+ const dest = path.join(process.cwd(), CFG_FILE);
285
+ if (fs.existsSync(dest)) {
286
+ console.log(yellow(`\n${CFG_FILE} already exists.\n`));
287
+ process.exit(0);
288
+ }
289
+ const starter = {
290
+ vars: { base: 'https://jsonplaceholder.typicode.com' },
291
+ timeout: 5000,
292
+ tests: [
293
+ {
294
+ name: 'Get post returns 200',
295
+ method: 'GET',
296
+ url: '{{base}}/posts/1',
297
+ expect: { status: 200, json: { id: 1 }, maxLatency: 2000 },
298
+ },
299
+ {
300
+ name: 'Create post',
301
+ method: 'POST',
302
+ url: '{{base}}/posts',
303
+ headers: { 'Content-Type': 'application/json' },
304
+ body: { title: 'Test', body: 'Hello', userId: 1 },
305
+ expect: { status: 201, json: { title: 'Test' } },
306
+ },
307
+ {
308
+ name: 'Non-existent post returns 404',
309
+ method: 'GET',
310
+ url: '{{base}}/posts/9999',
311
+ expect: { status: 404 },
312
+ },
313
+ ],
314
+ };
315
+ fs.writeFileSync(dest, JSON.stringify(starter, null, 2));
316
+ console.log(green(`\n✓ Created ${dest}\n`));
317
+ console.log(dim(' Edit the file then run: req-test\n'));
318
+ process.exit(0);
319
+ }
320
+
321
+ const cfgFile = positional[0]
322
+ ? path.resolve(positional[0])
323
+ : path.join(process.cwd(), CFG_FILE);
324
+
325
+ if (!fs.existsSync(cfgFile)) {
326
+ console.error(red(`\nTest file not found: ${cfgFile}`));
327
+ console.error(dim(` Run \`req-test init\` to create a starter ${CFG_FILE}\n`));
328
+ process.exit(1);
329
+ }
330
+
331
+ let suite;
332
+ try {
333
+ suite = JSON.parse(fs.readFileSync(cfgFile, 'utf8'));
334
+ } catch (e) {
335
+ console.error(red(`\nFailed to parse ${cfgFile}: ${e.message}\n`));
336
+ process.exit(1);
337
+ }
338
+
339
+ if (!suite.tests || !suite.tests.length) {
340
+ console.error(red('\nNo tests found in the suite.\n'));
341
+ process.exit(1);
342
+ }
343
+
344
+ const verbose = flags.has('--verbose');
345
+
346
+ console.log(`\n${bold('req-test')} ${cyan(path.basename(cfgFile))}`);
347
+ console.log(dim(` ${suite.tests.length} test(s) · base: ${(suite.vars || {}).base || ''}\n`));
348
+
349
+ runSuite(suite, suite.vars || {}).then(results => {
350
+ let passed = 0, failed = 0, skipped = 0, errors = 0;
351
+
352
+ for (const r of results) {
353
+ if (r.skipped) {
354
+ skipped++;
355
+ console.log(` ${dim('○')} ${dim(r.name)} ${dim('(skipped)')}`);
356
+ continue;
357
+ }
358
+ if (r.error) {
359
+ errors++;
360
+ console.log(` ${red('✘')} ${bold(r.name)} ${red('ERROR: ' + r.error)}`);
361
+ continue;
362
+ }
363
+ if (r.passed) {
364
+ passed++;
365
+ console.log(` ${green('✓')} ${r.name} ${dim(r.status + ' · ' + r.latency + 'ms')}`);
366
+ if (verbose && r.assertions.length) {
367
+ for (const a of r.assertions)
368
+ console.log(` ${green('·')} ${dim(a.message)}`);
369
+ }
370
+ } else {
371
+ failed++;
372
+ console.log(` ${red('✘')} ${bold(r.name)} ${dim(r.status + ' · ' + r.latency + 'ms')}`);
373
+ for (const a of r.assertions) {
374
+ if (!a.pass) console.log(` ${red('✘')} ${a.message}`);
375
+ else if (verbose) console.log(` ${green('✓')} ${dim(a.message)}`);
376
+ }
377
+ }
378
+ }
379
+
380
+ console.log();
381
+ const total = passed + failed + errors;
382
+ const summary = [
383
+ passed > 0 ? green(`${passed} passed`) : null,
384
+ failed > 0 ? red(`${failed} failed`) : null,
385
+ errors > 0 ? red(`${errors} error(s)`) : null,
386
+ skipped > 0 ? dim(`${skipped} skipped`) : null,
387
+ ].filter(Boolean).join(' ');
388
+
389
+ console.log(` ${summary} ${dim('of ' + (total + skipped) + ' tests')}\n`);
390
+
391
+ process.exit(failed > 0 || errors > 0 ? 1 : 0);
392
+ }).catch(e => {
393
+ console.error(red(`\nUnexpected error: ${e.message}\n`));
394
+ process.exit(1);
395
+ });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@lowdep/req-test",
3
+ "version": "1.0.0",
4
+ "description": "File-based HTTP test runner — define tests in JSON, run them like a test suite, zero dependencies",
5
+ "bin": {
6
+ "req-test": "bin/req-test.js"
7
+ },
8
+ "keywords": [
9
+ "http",
10
+ "testing",
11
+ "api",
12
+ "rest",
13
+ "cli",
14
+ "postman",
15
+ "newman",
16
+ "developer-tools",
17
+ "api testing",
18
+ "http test runner",
19
+ "postman alternative",
20
+ "newman alternative",
21
+ "rest client",
22
+ "api assertions",
23
+ "integration test",
24
+ "curl test",
25
+ "zero dependencies",
26
+ "ci"
27
+ ],
28
+ "author": "Rushabh Shah",
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=14"
32
+ },
33
+ "files": [
34
+ "bin/"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/Rushabh5000/req-test.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/Rushabh5000/req-test/issues"
42
+ },
43
+ "homepage": "https://github.com/Rushabh5000/req-test#readme",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }