test-proxy-recorder 0.3.0 → 0.3.1

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/dist/proxy.js CHANGED
@@ -23,7 +23,7 @@ function parseCliArgs() {
23
23
  "Port number for the proxy server",
24
24
  String(DEFAULT_PORT)
25
25
  ).option(
26
- "-r, --recordings-dir <path>",
26
+ "-d, --dir <path>",
27
27
  "Directory to store recordings (relative to CWD)",
28
28
  DEFAULT_RECORDINGS_DIR
29
29
  ).action(() => {
@@ -39,7 +39,7 @@ function parseCliArgs() {
39
39
  if (targets2.length === 0) {
40
40
  program.help();
41
41
  }
42
- const recordingsDir2 = path.resolve(process.cwd(), options.recordingsDir);
42
+ const recordingsDir2 = path.resolve(process.cwd(), options.dir);
43
43
  return { targets: targets2, port: port2, recordingsDir: recordingsDir2 };
44
44
  }
45
45
 
@@ -84,13 +84,23 @@ async function loadRecordingSession(filePath) {
84
84
  return JSON.parse(fileContent);
85
85
  }
86
86
  function processRecordings(recordings) {
87
- const keySequenceMap = /* @__PURE__ */ new Map();
88
- return recordings.map((recording) => {
87
+ const recordingsByKey = /* @__PURE__ */ new Map();
88
+ for (const recording of recordings) {
89
89
  const key = recording.key;
90
- const currentSeq = keySequenceMap.get(key) || 0;
91
- keySequenceMap.set(key, currentSeq + 1);
92
- return { ...recording, sequence: currentSeq };
93
- });
90
+ if (!recordingsByKey.has(key)) {
91
+ recordingsByKey.set(key, []);
92
+ }
93
+ recordingsByKey.get(key).push(recording);
94
+ }
95
+ const processedRecordings = [];
96
+ for (const [_key, keyRecordings] of recordingsByKey) {
97
+ keyRecordings.sort((a, b) => a.recordingId - b.recordingId);
98
+ keyRecordings.forEach((recording, index) => {
99
+ processedRecordings.push({ ...recording, sequence: index });
100
+ });
101
+ }
102
+ processedRecordings.sort((a, b) => a.recordingId - b.recordingId);
103
+ return processedRecordings;
94
104
  }
95
105
  async function saveRecordingSession(recordingsDir2, session) {
96
106
  const filePath = getRecordingPath(recordingsDir2, session.id);
@@ -153,19 +163,25 @@ var ProxyServer = class {
153
163
  recordingsDir;
154
164
  recordingIdCounter;
155
165
  // Unique ID for each recording entry
166
+ sequenceCounterByKey;
167
+ // Sequence counter per key (endpoint)
156
168
  replaySessions;
157
169
  // Track multiple concurrent replay sessions by recording ID
170
+ recordingPromises;
171
+ // Stack of promises that resolve to completed recordings
158
172
  constructor(targets2, recordingsDir2) {
159
173
  this.targets = targets2;
160
174
  this.currentTargetIndex = 0;
161
175
  this.mode = Modes.transparent;
162
176
  this.recordingId = null;
163
177
  this.recordingIdCounter = 0;
178
+ this.sequenceCounterByKey = /* @__PURE__ */ new Map();
164
179
  this.replayId = null;
165
180
  this.modeTimeout = null;
166
181
  this.currentSession = null;
167
182
  this.recordingsDir = recordingsDir2;
168
183
  this.replaySessions = /* @__PURE__ */ new Map();
184
+ this.recordingPromises = [];
169
185
  this.proxy = httpProxy.createProxyServer({
170
186
  secure: false,
171
187
  changeOrigin: true,
@@ -191,7 +207,7 @@ var ProxyServer = class {
191
207
  }
192
208
  setupProxyEventHandlers() {
193
209
  this.proxy.on("error", this.handleProxyError.bind(this));
194
- this.proxy.on("proxyRes", this.handleProxyResponse.bind(this));
210
+ this.proxy.on("proxyRes", this.addCorsHeaders.bind(this));
195
211
  }
196
212
  handleProxyError(err, req, res) {
197
213
  console.error("Proxy error:", err);
@@ -207,12 +223,6 @@ var ProxyServer = class {
207
223
  }
208
224
  res.end(JSON.stringify({ error: "Proxy error", message: err.message }));
209
225
  }
210
- handleProxyResponse(proxyRes, req) {
211
- this.addCorsHeaders(proxyRes, req);
212
- if (this.mode === Modes.record && this.recordingId) {
213
- this.recordResponse(req, proxyRes);
214
- }
215
- }
216
226
  /**
217
227
  * Get CORS headers for a given request
218
228
  * @param req The incoming HTTP request
@@ -306,18 +316,20 @@ var ProxyServer = class {
306
316
  }
307
317
  return { mode, id, timeout };
308
318
  }
319
+ async parseControlRequest(req) {
320
+ if (req.method === "GET") {
321
+ return this.parseGetParams(req);
322
+ }
323
+ if (req.method === "POST") {
324
+ const body = await readRequestBody(req);
325
+ console.log(`MODE CHANGE (${req.method})`, body);
326
+ return JSON.parse(body);
327
+ }
328
+ throw new Error("Unsupported control method");
329
+ }
309
330
  async handleControlRequest(req, res) {
310
331
  try {
311
- let data;
312
- if (req.method === "GET") {
313
- data = this.parseGetParams(req);
314
- } else if (req.method === "POST") {
315
- const body = await readRequestBody(req);
316
- console.log(`MODE CHANGE (${req.method})`, body);
317
- data = JSON.parse(body);
318
- } else {
319
- return;
320
- }
332
+ const data = await this.parseControlRequest(req);
321
333
  const { mode, id, timeout: requestTimeout } = data;
322
334
  const timeout = requestTimeout ?? DEFAULT_TIMEOUT_MS;
323
335
  this.clearModeTimeout();
@@ -350,7 +362,7 @@ var ProxyServer = class {
350
362
  async switchMode(mode, id) {
351
363
  console.log(`Switching to ${mode.toUpperCase()} mode`);
352
364
  if (this.currentSession && this.mode === Modes.record) {
353
- await this.saveCurrentSession(true);
365
+ await this.saveCurrentSession();
354
366
  console.log("Session saved, continuing with mode switch");
355
367
  }
356
368
  switch (mode) {
@@ -390,6 +402,8 @@ var ProxyServer = class {
390
402
  this.recordingId = id;
391
403
  this.replayId = null;
392
404
  this.currentSession = { id, recordings: [], websocketRecordings: [] };
405
+ this.recordingIdCounter = 0;
406
+ this.sequenceCounterByKey.clear();
393
407
  console.log(`Switched to record mode with ID: ${id}`);
394
408
  }
395
409
  async switchToReplayMode(id) {
@@ -409,146 +423,39 @@ var ProxyServer = class {
409
423
  setupModeTimeout(timeout) {
410
424
  this.modeTimeout = setTimeout(async () => {
411
425
  console.log("Timeout reached, switching back to transparent mode");
412
- await this.saveCurrentSession(true);
426
+ await this.saveCurrentSession();
413
427
  this.switchToTransparentMode();
414
428
  this.modeTimeout = null;
415
429
  }, timeout);
416
430
  }
417
- async saveCurrentSession(filterIncomplete = false) {
418
- if (!this.currentSession) {
431
+ async flushPendingRecordings() {
432
+ if (this.recordingPromises.length === 0) {
419
433
  return;
420
434
  }
421
- if (filterIncomplete) {
422
- const incompleteCount = this.currentSession.recordings.filter(
423
- (r) => !r.response
424
- ).length;
425
- if (incompleteCount > 0) {
426
- this.currentSession.recordings = this.currentSession.recordings.filter(
427
- (r) => r.response
428
- );
435
+ const results = await Promise.allSettled(this.recordingPromises);
436
+ if (this.currentSession) {
437
+ for (const result of results) {
438
+ if (result.status === "fulfilled" && result.value) {
439
+ this.currentSession.recordings.push(result.value);
440
+ }
429
441
  }
430
- }
431
- console.log(
432
- `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
433
- );
434
- await saveRecordingSession(this.recordingsDir, this.currentSession);
435
- }
436
- saveRequestRecordSync(req, body) {
437
- if (!this.currentSession) {
438
- return;
439
- }
440
- const key = getReqID(req);
441
- const recordingId = this.recordingIdCounter++;
442
- req.__recordingId = recordingId;
443
- const record = {
444
- request: {
445
- method: req.method,
446
- url: req.url,
447
- headers: req.headers,
448
- body: body || null
449
- },
450
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
451
- key,
452
- recordingId
453
- };
454
- this.currentSession.recordings.push(record);
455
- console.log(
456
- // eslint-disable-next-line sonarjs/no-nested-template-literals
457
- `saveRequestRecordSync: Saved ${req.method} ${req.url} (key: ${key}, recordingId: ${recordingId}, body: ${body ? `${body.length} chars` : "null"}, total: ${this.currentSession.recordings.length}, sessionId: ${this.currentSession.id})`
458
- );
459
- }
460
- updateRequestBodySync(req, body) {
461
- if (!this.currentSession) {
462
- return;
463
- }
464
- const recordingId = req.__recordingId;
465
- if (recordingId === void 0) {
466
- console.error(
467
- `updateRequestBodySync: No recording ID found on request ${req.method} ${req.url}`
468
- );
469
- return;
470
- }
471
- const record = this.currentSession.recordings.find(
472
- (r) => r.recordingId === recordingId
473
- );
474
- if (!record) {
475
- console.error(
476
- `updateRequestBodySync: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
442
+ console.log(
443
+ `Flushed ${results.length} recordings to session (total: ${this.currentSession.recordings.length})`
477
444
  );
478
- return;
479
445
  }
480
- record.request.body = body || null;
481
- console.log(
482
- `updateRequestBodySync: Updated body for ${req.method} ${req.url} (${body.length} chars, recordingId: ${recordingId})`
483
- );
446
+ this.recordingPromises = [];
484
447
  }
485
- async recordResponse(req, proxyRes) {
448
+ async saveCurrentSession() {
486
449
  if (!this.currentSession) {
487
450
  return;
488
451
  }
489
- const recordingId = req.__recordingId;
490
- if (recordingId === void 0) {
491
- console.error(
492
- `recordResponse: No recording ID found on request ${req.method} ${req.url}`
493
- );
494
- return;
495
- }
496
- const record = this.currentSession.recordings.find(
497
- (r) => r.recordingId === recordingId
498
- );
499
- if (!record) {
500
- console.error(
501
- `recordResponse: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
502
- );
503
- return;
504
- }
505
- const chunks = [];
506
- proxyRes.on("data", (chunk) => {
507
- chunks.push(chunk);
508
- });
509
- proxyRes.on("end", async () => {
510
- const body = Buffer.concat(chunks).toString("utf8");
511
- record.response = {
512
- statusCode: proxyRes.statusCode,
513
- headers: proxyRes.headers,
514
- body: body || null
515
- };
516
- console.log(
517
- `Recorded: ${req.method} ${req.url} (recordingId: ${recordingId})`
518
- );
519
- });
520
- }
521
- async recordResponseData(req, proxyRes, body) {
522
- if (!this.currentSession) {
523
- return false;
524
- }
525
- const recordingId = req.__recordingId;
526
- if (recordingId === void 0) {
527
- console.error(
528
- `recordResponseData: No recording ID found on request ${req.method} ${req.url}`
529
- );
530
- return false;
531
- }
532
- const record = this.currentSession.recordings.find(
533
- (r) => r.recordingId === recordingId
534
- );
535
- if (!record) {
536
- console.error(
537
- `recordResponseData: Could not find recording with ID ${recordingId} for ${req.method} ${req.url}`
538
- );
539
- return false;
540
- }
541
- record.response = {
542
- statusCode: proxyRes.statusCode,
543
- headers: proxyRes.headers,
544
- body: body || null
545
- };
452
+ await this.flushPendingRecordings();
546
453
  console.log(
547
- `recordResponseData: Recorded response for ${req.method} ${req.url} (recordingId: ${recordingId})`
454
+ `Saving session with ${this.currentSession.recordings.length} HTTP and ${this.currentSession.websocketRecordings.length} WebSocket recordings`
548
455
  );
549
- return true;
456
+ await saveRecordingSession(this.recordingsDir, this.currentSession);
550
457
  }
551
- async handleReplayRequest(req, res) {
458
+ getRecordingIdOrError(req, res) {
552
459
  const recordingId = this.getRecordingIdFromRequest(req) || this.replayId;
553
460
  if (!recordingId) {
554
461
  const corsHeaders = this.getCorsHeaders(req);
@@ -557,21 +464,50 @@ var ProxyServer = class {
557
464
  ...corsHeaders
558
465
  });
559
466
  res.end(JSON.stringify({ error: "No replay session active" }));
560
- return;
467
+ return null;
468
+ }
469
+ return recordingId;
470
+ }
471
+ async ensureSessionLoaded(recordingId, filePath) {
472
+ const sessionState = this.getOrCreateReplaySession(recordingId);
473
+ if (!sessionState.loadedSession) {
474
+ sessionState.loadedSession = await loadRecordingSession(filePath);
475
+ console.log(`[REPLAY] Loaded recording session: ${recordingId}`);
476
+ }
477
+ return sessionState;
478
+ }
479
+ getServedTracker(sessionState, key) {
480
+ if (!sessionState.servedRecordingIdsByKey.has(key)) {
481
+ sessionState.servedRecordingIdsByKey.set(key, /* @__PURE__ */ new Set());
482
+ }
483
+ return sessionState.servedRecordingIdsByKey.get(key);
484
+ }
485
+ selectReplayRecord(recordsWithKey, servedForThisKey, key, recordingId) {
486
+ for (const rec of recordsWithKey) {
487
+ if (!servedForThisKey.has(rec.recordingId)) {
488
+ return rec;
489
+ }
561
490
  }
491
+ if (recordsWithKey.length > 0) {
492
+ console.log(
493
+ `[REPLAY WARNING] All ${recordsWithKey.length} recordings already served for ${key} (session: ${recordingId}), reusing last one`
494
+ );
495
+ return recordsWithKey[recordsWithKey.length - 1];
496
+ }
497
+ return null;
498
+ }
499
+ async handleReplayRequest(req, res) {
500
+ const recordingId = this.getRecordingIdOrError(req, res);
501
+ if (!recordingId) return;
562
502
  const key = getReqID(req);
563
503
  const filePath = getRecordingPath(this.recordingsDir, recordingId);
564
504
  try {
565
- const sessionState = this.getOrCreateReplaySession(recordingId);
566
- if (!sessionState.loadedSession) {
567
- sessionState.loadedSession = await loadRecordingSession(filePath);
568
- console.log(`[REPLAY] Loaded recording session: ${recordingId}`);
569
- }
505
+ const sessionState = await this.ensureSessionLoaded(
506
+ recordingId,
507
+ filePath
508
+ );
570
509
  const session = sessionState.loadedSession;
571
- if (!sessionState.servedRecordingIdsByKey.has(key)) {
572
- sessionState.servedRecordingIdsByKey.set(key, /* @__PURE__ */ new Set());
573
- }
574
- const servedForThisKey = sessionState.servedRecordingIdsByKey.get(key);
510
+ const servedForThisKey = this.getServedTracker(sessionState, key);
575
511
  const host = req.headers.host || "unknown";
576
512
  const recordsWithKey = session.recordings.filter((r) => r.key === key && r.response).toSorted((a, b) => {
577
513
  const aSeq = a.sequence !== void 0 ? a.sequence : a.recordingId;
@@ -600,30 +536,23 @@ var ProxyServer = class {
600
536
  }
601
537
  const requestCount = servedForThisKey.size + 1;
602
538
  console.log(
603
- `[replay request #${requestCount}] ${req.method} ${req.url} (session: ${recordingId}, total: ${recordsWithKey.length}, served: ${servedForThisKey.size})`
539
+ `[replay request #${requestCount}] ${req.method} ${req.url} (key: ${key}, session: ${recordingId}, total: ${recordsWithKey.length}, served: ${servedForThisKey.size})`
604
540
  );
605
- let record;
606
- for (const rec of recordsWithKey) {
607
- if (!servedForThisKey.has(rec.recordingId)) {
608
- record = rec;
609
- break;
610
- }
611
- }
612
- if (!record) {
613
- console.log(
614
- `[REPLAY WARNING] All ${recordsWithKey.length} recordings already served for ${key} (session: ${recordingId}), reusing last one`
541
+ const record = this.selectReplayRecord(
542
+ recordsWithKey,
543
+ servedForThisKey,
544
+ key,
545
+ recordingId
546
+ );
547
+ if (!record || !record.response) {
548
+ throw new Error(
549
+ `No response recorded for this request: ${req.method} ${host}${req.url}`
615
550
  );
616
- record = recordsWithKey[recordsWithKey.length - 1];
617
551
  }
618
552
  servedForThisKey.add(record.recordingId);
619
553
  console.log(
620
554
  `[replay serving] recordingId: ${record.recordingId}, session: ${recordingId}, body_len: ${record.response?.body?.length || 0}`
621
555
  );
622
- if (!record.response) {
623
- throw new Error(
624
- `No response recorded for this request: ${req.method} ${host}${req.url}`
625
- );
626
- }
627
556
  const { statusCode, headers, body } = record.response;
628
557
  const responseHeaders = {
629
558
  ...headers,
@@ -678,82 +607,124 @@ var ProxyServer = class {
678
607
  const target = this.getTarget();
679
608
  console.log(`[${this.mode}] ${req.method} ${req.url} -> ${target}`);
680
609
  if (this.mode === Modes.record) {
681
- this.saveRequestRecordSync(req, null);
682
- await this.bufferAndProxyRequest(req, res, target);
610
+ await this.recordAndProxyRequest(req, res, target);
683
611
  } else {
684
612
  this.proxy.web(req, res, { target });
685
613
  }
686
614
  }
687
- // TODO: check if can handle streaming requests
688
- async bufferAndProxyRequest(req, res, target) {
689
- const chunks = [];
690
- req.on("data", (chunk) => {
691
- chunks.push(chunk);
692
- });
693
- try {
694
- await new Promise((resolve, reject) => {
695
- req.on("end", () => resolve());
696
- req.on("error", (err) => reject(err));
697
- setTimeout(
698
- () => reject(new Error("Request buffering timeout")),
699
- 3e4
700
- );
701
- });
702
- } catch (error) {
703
- console.error("Error buffering request:", error);
615
+ // Note: streaming requests are buffered before proxying; streaming passthrough is not yet implemented
616
+ async recordAndProxyRequest(req, res, target) {
617
+ if (!this.currentSession) {
618
+ return;
704
619
  }
705
- const body = Buffer.concat(chunks).toString("utf8");
706
- this.updateRequestBodySync(req, body);
707
- const targetUrl = new URL(target);
708
- const isHttps = targetUrl.protocol === "https:";
709
- const requestModule = isHttps ? https : http;
710
- const defaultPort = isHttps ? 443 : 80;
711
- const proxyReq = requestModule.request(
712
- {
713
- hostname: targetUrl.hostname,
714
- port: targetUrl.port || defaultPort,
715
- path: req.url,
716
- method: req.method,
717
- headers: req.headers
718
- },
719
- (proxyRes) => {
720
- this.addCorsHeaders(proxyRes, req);
721
- const responseChunks = [];
722
- proxyRes.on("data", (chunk) => {
723
- responseChunks.push(chunk);
724
- });
725
- proxyRes.on("end", async () => {
726
- const responseBody = Buffer.concat(responseChunks);
727
- const recorded = await this.recordResponseData(
728
- req,
729
- proxyRes,
730
- responseBody.toString("utf8")
731
- );
732
- const responseHeaders = {
733
- ...proxyRes.headers,
734
- ...this.getCorsHeaders(req)
735
- };
736
- res.writeHead(proxyRes.statusCode || 200, responseHeaders);
737
- res.end(responseBody);
738
- if (recorded) {
739
- console.log(`Recorded: ${req.method} ${req.url}`);
620
+ const key = getReqID(req);
621
+ const recordingId = this.recordingIdCounter++;
622
+ const sequence = this.sequenceCounterByKey.get(key) || 0;
623
+ this.sequenceCounterByKey.set(key, sequence + 1);
624
+ const recordingPromise = new Promise((resolve) => {
625
+ (async () => {
626
+ try {
627
+ const chunks = [];
628
+ req.on("data", (chunk) => {
629
+ chunks.push(chunk);
630
+ });
631
+ try {
632
+ await new Promise((resolveBuffer, rejectBuffer) => {
633
+ req.on("end", () => resolveBuffer());
634
+ req.on("error", (err) => rejectBuffer(err));
635
+ setTimeout(
636
+ () => rejectBuffer(new Error("Request buffering timeout")),
637
+ 3e4
638
+ );
639
+ });
640
+ } catch (error) {
641
+ console.error("Error buffering request:", error);
740
642
  }
741
- });
742
- proxyRes.on("error", (err) => {
743
- console.error("Proxy response error:", err);
744
- if (!res.headersSent) {
643
+ const requestBody = Buffer.concat(chunks).toString("utf8");
644
+ const targetUrl = new URL(target);
645
+ const isHttps = targetUrl.protocol === "https:";
646
+ const requestModule = isHttps ? https : http;
647
+ const defaultPort = isHttps ? 443 : 80;
648
+ const proxyReq = requestModule.request(
649
+ {
650
+ hostname: targetUrl.hostname,
651
+ port: targetUrl.port || defaultPort,
652
+ path: req.url,
653
+ method: req.method,
654
+ headers: req.headers
655
+ },
656
+ (proxyRes) => {
657
+ this.addCorsHeaders(proxyRes, req);
658
+ const responseChunks = [];
659
+ proxyRes.on("data", (chunk) => {
660
+ responseChunks.push(chunk);
661
+ });
662
+ proxyRes.on("end", async () => {
663
+ try {
664
+ const responseBody = Buffer.concat(responseChunks);
665
+ const responseBodyStr = responseBody.toString("utf8");
666
+ const recording = {
667
+ request: {
668
+ method: req.method,
669
+ url: req.url,
670
+ headers: req.headers,
671
+ body: requestBody || null
672
+ },
673
+ response: {
674
+ statusCode: proxyRes.statusCode,
675
+ headers: proxyRes.headers,
676
+ body: responseBodyStr || null
677
+ },
678
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
679
+ key,
680
+ recordingId,
681
+ sequence
682
+ };
683
+ const responseHeaders = {
684
+ ...proxyRes.headers,
685
+ ...this.getCorsHeaders(req)
686
+ };
687
+ res.writeHead(proxyRes.statusCode || 200, responseHeaders);
688
+ res.end(responseBody);
689
+ console.log(
690
+ `Recorded: ${req.method} ${req.url} (recordingId: ${recordingId}, sequence: ${sequence})`
691
+ );
692
+ resolve(recording);
693
+ } catch (error) {
694
+ console.error("Error completing recording:", error);
695
+ resolve(null);
696
+ }
697
+ });
698
+ proxyRes.on("error", (err) => {
699
+ console.error("Proxy response error:", err);
700
+ if (!res.headersSent) {
701
+ this.handleProxyError(err, req, res);
702
+ }
703
+ resolve(null);
704
+ });
705
+ }
706
+ );
707
+ proxyReq.on("error", (err) => {
745
708
  this.handleProxyError(err, req, res);
709
+ resolve(null);
710
+ });
711
+ if (chunks.length > 0) {
712
+ proxyReq.write(Buffer.concat(chunks));
746
713
  }
747
- });
748
- }
749
- );
750
- proxyReq.on("error", (err) => {
751
- this.handleProxyError(err, req, res);
714
+ proxyReq.end();
715
+ } catch (error) {
716
+ console.error("Error in recordAndProxyRequest:", error);
717
+ try {
718
+ this.handleProxyError(error, req, res);
719
+ } catch (error_) {
720
+ console.error("Failed to handle proxy error:", error_);
721
+ }
722
+ resolve(null);
723
+ }
724
+ })();
752
725
  });
753
- if (chunks.length > 0) {
754
- proxyReq.write(Buffer.concat(chunks));
755
- }
756
- proxyReq.end();
726
+ this.recordingPromises.push(recordingPromise);
727
+ await recordingPromise;
757
728
  }
758
729
  handleUpgrade(req, socket, head) {
759
730
  if (this.mode === Modes.replay) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "test-proxy-recorder",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "HTTP proxy server for recording and replaying network requests in testing. Works seamlessly with Playwright testing framework.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",