yo-bug 0.1.2 → 0.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.
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'http';
2
2
  /**
3
3
  * Middleware that intercepts HTML responses from the proxy
4
4
  * and injects the vibe-feedback SDK script before </body>.
5
+ * Only intercepts 2xx HTML responses — redirects and errors pass through.
5
6
  */
6
7
  export declare function createHtmlInjector(proxyRes: IncomingMessage, res: ServerResponse): {
7
8
  onData(chunk: Buffer): void;
@@ -1 +1 @@
1
- {"version":3,"file":"html-injector.d.ts","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAI5D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc;kBAc/D,MAAM;;SAkBvB"}
1
+ {"version":3,"file":"html-injector.d.ts","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAI5D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc;kBAmB/D,MAAM;;SAiBvB"}
@@ -2,14 +2,19 @@ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
2
2
  /**
3
3
  * Middleware that intercepts HTML responses from the proxy
4
4
  * and injects the vibe-feedback SDK script before </body>.
5
+ * Only intercepts 2xx HTML responses — redirects and errors pass through.
5
6
  */
6
7
  export function createHtmlInjector(proxyRes, res) {
8
+ const statusCode = proxyRes.statusCode || 200;
7
9
  const contentType = proxyRes.headers['content-type'] || '';
8
- if (!contentType.includes('text/html')) {
9
- return null; // Not HTML, don't intercept
10
+ // Only inject into 2xx HTML responses
11
+ // Don't touch redirects (3xx), errors (4xx/5xx), or non-HTML
12
+ if (statusCode < 200 || statusCode >= 300 || !contentType.includes('text/html')) {
13
+ return null;
10
14
  }
11
15
  // Copy all response headers except content-length (we'll modify the body)
12
- res.writeHead(proxyRes.statusCode || 200, Object.fromEntries(Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length')));
16
+ const headers = Object.fromEntries(Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length'));
17
+ res.writeHead(statusCode, headers);
13
18
  const chunks = [];
14
19
  return {
15
20
  onData(chunk) {
@@ -17,7 +22,6 @@ export function createHtmlInjector(proxyRes, res) {
17
22
  },
18
23
  onEnd() {
19
24
  let html = Buffer.concat(chunks).toString('utf-8');
20
- // Inject before </body> or at end if no </body>
21
25
  if (html.includes('</body>')) {
22
26
  html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
23
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"html-injector.js","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,2CAA2C,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAyB,EAAE,GAAmB;IAC/E,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC,CAAC,4BAA4B;IAC3C,CAAC;IAED,0EAA0E;IAC1E,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,EAAE,MAAM,CAAC,WAAW,CAC1D,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAC7E,CAAC,CAAC;IAEH,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,OAAO;QACL,MAAM,CAAC,KAAa;YAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,KAAK;YACH,IAAI,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAEnD,gDAAgD;YAChD,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACpC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC/B,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"html-injector.js","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,2CAA2C,CAAC;AAElE;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAyB,EAAE,GAAmB;IAC/E,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,IAAI,GAAG,CAAC;IAC9C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAE3D,sCAAsC;IACtC,6DAA6D;IAC7D,IAAI,UAAU,GAAG,GAAG,IAAI,UAAU,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0EAA0E;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAChC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAC7E,CAAC;IACF,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,OAAO;QACL,MAAM,CAAC,KAAa;YAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,KAAK;YACH,IAAI,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAEnD,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACpC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC/B,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0ElD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
1
+ {"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwGlD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
@@ -3,8 +3,8 @@ import cors from 'cors';
3
3
  import httpProxy from 'http-proxy';
4
4
  import { createFeedbackRouter } from './feedback-api.js';
5
5
  import { createSdkRouter } from './sdk-serve.js';
6
- import { createHtmlInjector } from './html-injector.js';
7
6
  const PROXY_PORT = 3695;
7
+ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
8
8
  let server = null;
9
9
  let proxy = null;
10
10
  export async function startProxyServer(targetPort, store) {
@@ -14,45 +14,77 @@ export async function startProxyServer(targetPort, store) {
14
14
  const targetUrl = `http://localhost:${targetPort}`;
15
15
  const proxyUrl = `http://localhost:${PROXY_PORT}`;
16
16
  const app = express();
17
- // CORS for SDK to submit feedback
18
17
  app.use(cors());
19
- // Parse JSON for feedback API
20
18
  app.use(express.json({ limit: '10mb' }));
21
- // Serve SDK JS file
19
+ // Our own routes — served directly, not proxied
22
20
  app.use(createSdkRouter());
23
- // Feedback API routes
24
21
  app.use(createFeedbackRouter(store));
25
- // Create proxy
22
+ // Create proxy — let it handle responses by default (no selfHandleResponse)
26
23
  proxy = httpProxy.createProxyServer({
27
24
  target: targetUrl,
28
- ws: true, // WebSocket support for HMR
25
+ ws: true,
29
26
  changeOrigin: true,
30
27
  });
31
28
  proxy.on('error', (_err, _req, res) => {
32
29
  if (res && 'writeHead' in res) {
33
- res.writeHead?.(502, { 'Content-Type': 'text/plain' });
34
- res.end?.('Dev server not responding');
30
+ try {
31
+ res.writeHead?.(502, { 'Content-Type': 'text/plain' });
32
+ res.end?.('Dev server not responding');
33
+ }
34
+ catch { }
35
35
  }
36
36
  });
37
- // Intercept proxy responses to inject SDK into HTML
37
+ // Inject SDK into HTML responses using the modifyResponse approach
38
+ // We check Accept header on REQUEST to decide if we need to intercept
39
+ app.all('*', (req, res) => {
40
+ const accept = req.headers['accept'] || '';
41
+ const isHtmlRequest = accept.includes('text/html');
42
+ if (isHtmlRequest) {
43
+ // Potentially an HTML page — intercept to inject SDK
44
+ proxy.web(req, res, { selfHandleResponse: true });
45
+ }
46
+ else {
47
+ // Static asset (JS/CSS/images/fonts/etc) — let proxy handle directly
48
+ proxy.web(req, res);
49
+ }
50
+ });
51
+ // Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
38
52
  proxy.on('proxyRes', (proxyRes, _req, res) => {
39
- const injector = createHtmlInjector(proxyRes, res);
40
- if (injector) {
41
- // HTML response buffer and inject
42
- proxyRes.on('data', (chunk) => injector.onData(chunk));
43
- proxyRes.on('end', () => injector.onEnd());
53
+ const statusCode = proxyRes.statusCode || 200;
54
+ const contentType = proxyRes.headers['content-type'] || '';
55
+ // Only inject into 2xx HTML responses
56
+ if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
57
+ // Buffer response, inject SDK, send
58
+ const chunks = [];
59
+ proxyRes.on('data', (chunk) => chunks.push(chunk));
60
+ proxyRes.on('end', () => {
61
+ let html = Buffer.concat(chunks).toString('utf-8');
62
+ if (html.includes('</body>')) {
63
+ html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
64
+ }
65
+ else if (html.includes('</html>')) {
66
+ html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
67
+ }
68
+ else {
69
+ html += `\n${INJECT_SCRIPT}`;
70
+ }
71
+ // Copy headers except content-length (body size changed)
72
+ const headers = { ...proxyRes.headers };
73
+ delete headers['content-length'];
74
+ // Remove content-encoding since we decoded the buffer as utf-8
75
+ delete headers['content-encoding'];
76
+ delete headers['transfer-encoding'];
77
+ headers['content-length'] = String(Buffer.byteLength(html));
78
+ res.writeHead(statusCode, headers);
79
+ res.end(html);
80
+ });
44
81
  }
45
82
  else {
46
- // Non-HTML — pipe through directly
83
+ // Redirect or non-HTML — pass through as-is
84
+ res.writeHead(statusCode, proxyRes.headers);
47
85
  proxyRes.pipe(res);
48
86
  }
49
87
  });
50
- // All other requests → proxy to dev server
51
- app.all('*', (req, res) => {
52
- proxy.web(req, res, {
53
- selfHandleResponse: true, // We handle response in proxyRes event
54
- });
55
- });
56
88
  // Start server
57
89
  await new Promise((resolve, reject) => {
58
90
  server = app.listen(PROXY_PORT, () => resolve());
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yo-bug",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "MCP Server for visual test feedback in vibe coding — QA capability as a protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +1,13 @@
1
1
  import express from 'express';
2
2
  import cors from 'cors';
3
3
  import httpProxy from 'http-proxy';
4
- import type { Server } from 'http';
4
+ import type { Server, IncomingMessage, ServerResponse } from 'http';
5
5
  import { FeedbackStore } from '../storage/store.js';
6
6
  import { createFeedbackRouter } from './feedback-api.js';
7
7
  import { createSdkRouter } from './sdk-serve.js';
8
- import { createHtmlInjector } from './html-injector.js';
9
8
 
10
9
  const PROXY_PORT = 3695;
10
+ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
11
11
 
12
12
  let server: Server | null = null;
13
13
  let proxy: httpProxy | null = null;
@@ -24,51 +24,81 @@ export async function startProxyServer(
24
24
  const proxyUrl = `http://localhost:${PROXY_PORT}`;
25
25
 
26
26
  const app = express();
27
-
28
- // CORS for SDK to submit feedback
29
27
  app.use(cors());
30
-
31
- // Parse JSON for feedback API
32
28
  app.use(express.json({ limit: '10mb' }));
33
29
 
34
- // Serve SDK JS file
30
+ // Our own routes — served directly, not proxied
35
31
  app.use(createSdkRouter());
36
-
37
- // Feedback API routes
38
32
  app.use(createFeedbackRouter(store));
39
33
 
40
- // Create proxy
34
+ // Create proxy — let it handle responses by default (no selfHandleResponse)
41
35
  proxy = httpProxy.createProxyServer({
42
36
  target: targetUrl,
43
- ws: true, // WebSocket support for HMR
37
+ ws: true,
44
38
  changeOrigin: true,
45
39
  });
46
40
 
47
41
  proxy.on('error', (_err, _req, res) => {
48
42
  if (res && 'writeHead' in res) {
49
- (res as any).writeHead?.(502, { 'Content-Type': 'text/plain' });
50
- (res as any).end?.('Dev server not responding');
43
+ try {
44
+ (res as any).writeHead?.(502, { 'Content-Type': 'text/plain' });
45
+ (res as any).end?.('Dev server not responding');
46
+ } catch {}
51
47
  }
52
48
  });
53
49
 
54
- // Intercept proxy responses to inject SDK into HTML
55
- proxy.on('proxyRes', (proxyRes, _req, res) => {
56
- const injector = createHtmlInjector(proxyRes, res as any);
57
- if (injector) {
58
- // HTML response — buffer and inject
59
- proxyRes.on('data', (chunk) => injector.onData(chunk));
60
- proxyRes.on('end', () => injector.onEnd());
50
+ // Inject SDK into HTML responses using the modifyResponse approach
51
+ // We check Accept header on REQUEST to decide if we need to intercept
52
+ app.all('*', (req, res) => {
53
+ const accept = req.headers['accept'] || '';
54
+ const isHtmlRequest = accept.includes('text/html');
55
+
56
+ if (isHtmlRequest) {
57
+ // Potentially an HTML page — intercept to inject SDK
58
+ proxy!.web(req, res, { selfHandleResponse: true });
61
59
  } else {
62
- // Non-HTMLpipe through directly
63
- proxyRes.pipe(res as any);
60
+ // Static asset (JS/CSS/images/fonts/etc) let proxy handle directly
61
+ proxy!.web(req, res);
64
62
  }
65
63
  });
66
64
 
67
- // All other requests proxy to dev server
68
- app.all('*', (req, res) => {
69
- proxy!.web(req, res, {
70
- selfHandleResponse: true, // We handle response in proxyRes event
71
- });
65
+ // Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
66
+ proxy.on('proxyRes', (proxyRes, _req, res) => {
67
+ const statusCode = proxyRes.statusCode || 200;
68
+ const contentType = proxyRes.headers['content-type'] || '';
69
+
70
+ // Only inject into 2xx HTML responses
71
+ if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
72
+ // Buffer response, inject SDK, send
73
+ const chunks: Buffer[] = [];
74
+ proxyRes.on('data', (chunk) => chunks.push(chunk));
75
+ proxyRes.on('end', () => {
76
+ let html = Buffer.concat(chunks).toString('utf-8');
77
+
78
+ if (html.includes('</body>')) {
79
+ html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
80
+ } else if (html.includes('</html>')) {
81
+ html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
82
+ } else {
83
+ html += `\n${INJECT_SCRIPT}`;
84
+ }
85
+
86
+ // Copy headers except content-length (body size changed)
87
+ const headers = { ...proxyRes.headers };
88
+ delete headers['content-length'];
89
+ // Remove content-encoding since we decoded the buffer as utf-8
90
+ delete headers['content-encoding'];
91
+ delete headers['transfer-encoding'];
92
+ headers['content-length'] = String(Buffer.byteLength(html));
93
+
94
+ res.writeHead(statusCode, headers);
95
+ res.end(html);
96
+ });
97
+ } else {
98
+ // Redirect or non-HTML — pass through as-is
99
+ res.writeHead(statusCode, proxyRes.headers);
100
+ proxyRes.pipe(res);
101
+ }
72
102
  });
73
103
 
74
104
  // Start server
@@ -1,41 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'http';
2
-
3
- const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
4
-
5
- /**
6
- * Middleware that intercepts HTML responses from the proxy
7
- * and injects the vibe-feedback SDK script before </body>.
8
- */
9
- export function createHtmlInjector(proxyRes: IncomingMessage, res: ServerResponse) {
10
- const contentType = proxyRes.headers['content-type'] || '';
11
- if (!contentType.includes('text/html')) {
12
- return null; // Not HTML, don't intercept
13
- }
14
-
15
- // Copy all response headers except content-length (we'll modify the body)
16
- res.writeHead(proxyRes.statusCode || 200, Object.fromEntries(
17
- Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length')
18
- ));
19
-
20
- const chunks: Buffer[] = [];
21
-
22
- return {
23
- onData(chunk: Buffer) {
24
- chunks.push(chunk);
25
- },
26
- onEnd() {
27
- let html = Buffer.concat(chunks).toString('utf-8');
28
-
29
- // Inject before </body> or at end if no </body>
30
- if (html.includes('</body>')) {
31
- html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
32
- } else if (html.includes('</html>')) {
33
- html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
34
- } else {
35
- html += `\n${INJECT_SCRIPT}`;
36
- }
37
-
38
- res.end(html);
39
- },
40
- };
41
- }