twd-relay 0.1.0 → 0.2.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.
@@ -1 +1 @@
1
- "use strict";var k=Object.create;var T=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var P=Object.getPrototypeOf,A=Object.prototype.hasOwnProperty;var L=(n,o,c,d)=>{if(o&&typeof o=="object"||typeof o=="function")for(let a of D(o))!A.call(n,a)&&a!==c&&T(n,a,{get:()=>o[a],enumerable:!(d=b(o,a))||d.enumerable});return n};var $=(n,o,c)=>(c=n!=null?k(P(n)):{},L(o||!n||!n.__esModule?T(c,"default",{value:n,enumerable:!0}):c,n));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function I(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/__twd/ws`}function p(n,o){if(!n.parent)return"";const c=o.get(n.parent);return c?c.name:""}function M(n){const o=n?.url??I(),c=n?.reconnect??!0,d=n?.reconnectInterval??2e3,a="[twd-relay]";let t=null,m=!1,w=null;function i(r){t&&t.readyState===WebSocket.OPEN&&t.send(JSON.stringify(r))}function u(){window.dispatchEvent(new CustomEvent("twd:state-change"))}async function _(){const r=window.__TWD_STATE__;if(!r){i({type:"error",code:"NO_TWD",message:"TWD not initialized"});return}const s=r.handlers,y=Array.from(s.values()).filter(e=>e.type==="test").length;i({type:"run:start",testCount:y});let l=0,h=0,E=0;const g=performance.now(),O={onStart(e){e.status="running",u(),i({type:"test:start",id:e.id,name:e.name,suite:p(e,s)})},onPass(e){l++,e.status="pass",u(),i({type:"test:pass",id:e.id,name:e.name,suite:p(e,s),duration:performance.now()-g})},onFail(e,f){h++,e.status="fail",e.logs=[f.message],u(),i({type:"test:fail",id:e.id,name:e.name,suite:p(e,s),error:f.message,duration:performance.now()-g})},onSkip(e){E++,e.status="skip",u(),i({type:"test:skip",id:e.id,name:e.name,suite:p(e,s)})},onSuiteStart(e){e.status="running",u()},onSuiteEnd(e){e.status="idle",u()}};try{const{TestRunner:e}=await import("twd-js/runner");await new e(O).runAll()}catch(e){const f=e instanceof Error?e.message:String(e);i({type:"error",code:"RUNNER_ERROR",message:f})}const R=performance.now()-g;i({type:"run:complete",passed:l,failed:h,skipped:E,duration:R}),u()}function C(){const r=window.__TWD_STATE__;if(!r){i({type:"error",code:"NO_TWD",message:"TWD not initialized"});return}const s=r.handlers,y=[];for(const[,l]of s)l.type==="test"&&y.push({id:l.id,name:l.name,suite:p(l,s),status:l.status??"idle"});i({type:"status:result",tests:y})}function N(r){let s;try{s=JSON.parse(r.data)}catch{return}s.type==="run"?(console.info(a,"Received run command — running tests..."),_()):s.type==="status"&&C()}function v(){c&&!m&&(console.info(a,`Reconnecting in ${d}ms...`),w=setTimeout(()=>{S()},d))}function S(){t&&(t.readyState===WebSocket.OPEN||t.readyState===WebSocket.CONNECTING)||(m=!1,console.info(a,"Connecting to",o),t=new WebSocket(o),t.addEventListener("open",()=>{i({type:"hello",role:"browser"}),console.info(a,"Connected to relay — ready to receive run/status commands")}),t.addEventListener("message",N),t.addEventListener("close",r=>{t=null,m||console.info(a,"Disconnected",r.code?`(code ${r.code})`:"",r.reason||""),v()}),t.addEventListener("error",()=>{}))}function W(){m=!0,w&&(clearTimeout(w),w=null),t&&(t.close(1e3,"Client disconnecting"),t=null)}return{connect:S,disconnect:W,get connected(){return t!==null&&t.readyState===WebSocket.OPEN}}}exports.createBrowserClient=M;
1
+ "use strict";var P=Object.create;var C=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var L=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var I=(n,o,i,f)=>{if(o&&typeof o=="object"||typeof o=="function")for(let l of L(o))!z.call(n,l)&&l!==i&&C(n,l,{get:()=>o[l],enumerable:!(f=A(o,l))||f.enumerable});return n};var M=(n,o,i)=>(i=n!=null?P($(n)):{},I(o||!n||!n.__esModule?C(i,"default",{value:n,enumerable:!0}):i,n));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function j(){return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/__twd/ws`}function p(n,o){if(!n.parent)return"";const i=o.get(n.parent);return i?i.name:""}function B(n){const o=n?.url??j(),i=n?.reconnect??!0,f=n?.reconnectInterval??2e3,l=n?.log??!1,T="[twd-relay]";function m(...t){l&&console.info(T,...t)}function S(...t){console.warn(T,...t)}let r=null,w=!1,g=null;function a(t){r&&r.readyState===WebSocket.OPEN&&r.send(JSON.stringify(t))}function u(){window.dispatchEvent(new CustomEvent("twd:state-change"))}async function N(){const t=window.__TWD_STATE__;if(!t){S("TWD not initialized — make sure twd-js is loaded before running tests"),a({type:"error",code:"NO_TWD",message:"TWD not initialized"});return}const s=t.handlers,y=Array.from(s.values()).filter(e=>e.type==="test").length;a({type:"run:start",testCount:y});let c=0,_=0,b=0;const h=performance.now(),O={onStart(e){e.status="running",u(),a({type:"test:start",id:e.id,name:e.name,suite:p(e,s)})},onPass(e){c++,e.status="pass",u(),a({type:"test:pass",id:e.id,name:e.name,suite:p(e,s),duration:performance.now()-h})},onFail(e,d){_++,e.status="fail",e.logs=[d.message],u(),a({type:"test:fail",id:e.id,name:e.name,suite:p(e,s),error:d.message,duration:performance.now()-h})},onSkip(e){b++,e.status="skip",u(),a({type:"test:skip",id:e.id,name:e.name,suite:p(e,s)})},onSuiteStart(e){e.status="running",u()},onSuiteEnd(e){e.status="idle",u()}};try{const{TestRunner:e}=await import("twd-js/runner");await new e(O).runAll()}catch(e){const d=e instanceof Error?e.message:String(e);S("Runner error:",d),a({type:"error",code:"RUNNER_ERROR",message:d})}const D=performance.now()-h;a({type:"run:complete",passed:c,failed:_,skipped:b,duration:D}),u()}function R(){const t=window.__TWD_STATE__;if(!t){a({type:"error",code:"NO_TWD",message:"TWD not initialized"});return}const s=t.handlers,y=[];for(const[,c]of s)c.type==="test"&&y.push({id:c.id,name:c.name,suite:p(c,s),status:c.status??"idle"});a({type:"status:result",tests:y})}function W(t){let s;try{s=JSON.parse(t.data)}catch{return}s.type==="run"?(m("Received run command — running tests..."),N()):s.type==="status"&&R()}function k(){i&&!w&&(m(`Reconnecting in ${f}ms...`),g=setTimeout(()=>{E()},f))}function E(){r&&(r.readyState===WebSocket.OPEN||r.readyState===WebSocket.CONNECTING)||(w=!1,m("Connecting to",o),r=new WebSocket(o),r.addEventListener("open",()=>{a({type:"hello",role:"browser"}),m("Connected to relay — ready to receive run/status commands")}),r.addEventListener("message",W),r.addEventListener("close",t=>{if(r=null,t.reason==="Replaced by new browser"){S("Another browser instance connected — this instance will not reconnect");return}w||m("Disconnected",t.code?`(code ${t.code})`:"",t.reason||""),k()}),r.addEventListener("error",()=>{}))}function v(){w=!0,g&&(clearTimeout(g),g=null),r&&(r.close(1e3,"Client disconnecting"),r=null)}return{connect:E,disconnect:v,get connected(){return r!==null&&r.readyState===WebSocket.OPEN}}}exports.createBrowserClient=B;
package/dist/browser.d.ts CHANGED
@@ -11,6 +11,8 @@ export declare interface BrowserClientOptions {
11
11
  reconnect?: boolean;
12
12
  /** Milliseconds between reconnect attempts. Default: 2000 */
13
13
  reconnectInterval?: number;
14
+ /** Enable console logging. Default: false */
15
+ log?: boolean;
14
16
  }
15
17
 
16
18
  export declare function createBrowserClient(options?: BrowserClientOptions): BrowserClient;
@@ -1,29 +1,35 @@
1
- function k() {
1
+ function O() {
2
2
  return `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/__twd/ws`;
3
3
  }
4
- function l(a, d) {
5
- if (!a.parent) return "";
6
- const f = d.get(a.parent);
4
+ function l(s, d) {
5
+ if (!s.parent) return "";
6
+ const f = d.get(s.parent);
7
7
  return f ? f.name : "";
8
8
  }
9
- function O(a) {
10
- const d = a?.url ?? k(), f = a?.reconnect ?? !0, g = a?.reconnectInterval ?? 2e3, c = "[twd-relay]";
11
- let n = null, p = !1, m = null;
12
- function r(t) {
13
- n && n.readyState === WebSocket.OPEN && n.send(JSON.stringify(t));
9
+ function A(s) {
10
+ const d = s?.url ?? O(), f = s?.reconnect ?? !0, S = s?.reconnectInterval ?? 2e3, N = s?.log ?? !1, h = "[twd-relay]";
11
+ function u(...n) {
12
+ N && console.info(h, ...n);
13
+ }
14
+ function g(...n) {
15
+ console.warn(h, ...n);
16
+ }
17
+ let t = null, p = !1, m = null;
18
+ function r(n) {
19
+ t && t.readyState === WebSocket.OPEN && t.send(JSON.stringify(n));
14
20
  }
15
21
  function i() {
16
22
  window.dispatchEvent(new CustomEvent("twd:state-change"));
17
23
  }
18
- async function T() {
19
- const t = window.__TWD_STATE__;
20
- if (!t) {
21
- r({ type: "error", code: "NO_TWD", message: "TWD not initialized" });
24
+ async function C() {
25
+ const n = window.__TWD_STATE__;
26
+ if (!n) {
27
+ g("TWD not initialized — make sure twd-js is loaded before running tests"), r({ type: "error", code: "NO_TWD", message: "TWD not initialized" });
22
28
  return;
23
29
  }
24
- const o = t.handlers, w = Array.from(o.values()).filter((e) => e.type === "test").length;
30
+ const o = n.handlers, w = Array.from(o.values()).filter((e) => e.type === "test").length;
25
31
  r({ type: "run:start", testCount: w });
26
- let s = 0, h = 0, E = 0;
32
+ let a = 0, T = 0, _ = 0;
27
33
  const y = performance.now(), v = {
28
34
  onStart(e) {
29
35
  e.status = "running", i(), r({
@@ -34,7 +40,7 @@ function O(a) {
34
40
  });
35
41
  },
36
42
  onPass(e) {
37
- s++, e.status = "pass", i(), r({
43
+ a++, e.status = "pass", i(), r({
38
44
  type: "test:pass",
39
45
  id: e.id,
40
46
  name: e.name,
@@ -42,18 +48,18 @@ function O(a) {
42
48
  duration: performance.now() - y
43
49
  });
44
50
  },
45
- onFail(e, u) {
46
- h++, e.status = "fail", e.logs = [u.message], i(), r({
51
+ onFail(e, c) {
52
+ T++, e.status = "fail", e.logs = [c.message], i(), r({
47
53
  type: "test:fail",
48
54
  id: e.id,
49
55
  name: e.name,
50
56
  suite: l(e, o),
51
- error: u.message,
57
+ error: c.message,
52
58
  duration: performance.now() - y
53
59
  });
54
60
  },
55
61
  onSkip(e) {
56
- E++, e.status = "skip", i(), r({
62
+ _++, e.status = "skip", i(), r({
57
63
  type: "test:skip",
58
64
  id: e.id,
59
65
  name: e.name,
@@ -71,61 +77,65 @@ function O(a) {
71
77
  const { TestRunner: e } = await import("twd-js/runner");
72
78
  await new e(v).runAll();
73
79
  } catch (e) {
74
- const u = e instanceof Error ? e.message : String(e);
75
- r({ type: "error", code: "RUNNER_ERROR", message: u });
80
+ const c = e instanceof Error ? e.message : String(e);
81
+ g("Runner error:", c), r({ type: "error", code: "RUNNER_ERROR", message: c });
76
82
  }
77
- const R = performance.now() - y;
78
- r({ type: "run:complete", passed: s, failed: h, skipped: E, duration: R }), i();
83
+ const D = performance.now() - y;
84
+ r({ type: "run:complete", passed: a, failed: T, skipped: _, duration: D }), i();
79
85
  }
80
- function _() {
81
- const t = window.__TWD_STATE__;
82
- if (!t) {
86
+ function R() {
87
+ const n = window.__TWD_STATE__;
88
+ if (!n) {
83
89
  r({ type: "error", code: "NO_TWD", message: "TWD not initialized" });
84
90
  return;
85
91
  }
86
- const o = t.handlers, w = [];
87
- for (const [, s] of o)
88
- s.type === "test" && w.push({
89
- id: s.id,
90
- name: s.name,
91
- suite: l(s, o),
92
- status: s.status ?? "idle"
92
+ const o = n.handlers, w = [];
93
+ for (const [, a] of o)
94
+ a.type === "test" && w.push({
95
+ id: a.id,
96
+ name: a.name,
97
+ suite: l(a, o),
98
+ status: a.status ?? "idle"
93
99
  });
94
100
  r({ type: "status:result", tests: w });
95
101
  }
96
- function N(t) {
102
+ function W(n) {
97
103
  let o;
98
104
  try {
99
- o = JSON.parse(t.data);
105
+ o = JSON.parse(n.data);
100
106
  } catch {
101
107
  return;
102
108
  }
103
- o.type === "run" ? (console.info(c, "Received run command — running tests..."), T()) : o.type === "status" && _();
109
+ o.type === "run" ? (u("Received run command — running tests..."), C()) : o.type === "status" && R();
104
110
  }
105
- function C() {
106
- f && !p && (console.info(c, `Reconnecting in ${g}ms...`), m = setTimeout(() => {
107
- S();
108
- }, g));
111
+ function b() {
112
+ f && !p && (u(`Reconnecting in ${S}ms...`), m = setTimeout(() => {
113
+ E();
114
+ }, S));
109
115
  }
110
- function S() {
111
- n && (n.readyState === WebSocket.OPEN || n.readyState === WebSocket.CONNECTING) || (p = !1, console.info(c, "Connecting to", d), n = new WebSocket(d), n.addEventListener("open", () => {
112
- r({ type: "hello", role: "browser" }), console.info(c, "Connected to relay — ready to receive run/status commands");
113
- }), n.addEventListener("message", N), n.addEventListener("close", (t) => {
114
- n = null, p || console.info(c, "Disconnected", t.code ? `(code ${t.code})` : "", t.reason || ""), C();
115
- }), n.addEventListener("error", () => {
116
+ function E() {
117
+ t && (t.readyState === WebSocket.OPEN || t.readyState === WebSocket.CONNECTING) || (p = !1, u("Connecting to", d), t = new WebSocket(d), t.addEventListener("open", () => {
118
+ r({ type: "hello", role: "browser" }), u("Connected to relay — ready to receive run/status commands");
119
+ }), t.addEventListener("message", W), t.addEventListener("close", (n) => {
120
+ if (t = null, n.reason === "Replaced by new browser") {
121
+ g("Another browser instance connected — this instance will not reconnect");
122
+ return;
123
+ }
124
+ p || u("Disconnected", n.code ? `(code ${n.code})` : "", n.reason || ""), b();
125
+ }), t.addEventListener("error", () => {
116
126
  }));
117
127
  }
118
- function W() {
119
- p = !0, m && (clearTimeout(m), m = null), n && (n.close(1e3, "Client disconnecting"), n = null);
128
+ function k() {
129
+ p = !0, m && (clearTimeout(m), m = null), t && (t.close(1e3, "Client disconnecting"), t = null);
120
130
  }
121
131
  return {
122
- connect: S,
123
- disconnect: W,
132
+ connect: E,
133
+ disconnect: k,
124
134
  get connected() {
125
- return n !== null && n.readyState === WebSocket.OPEN;
135
+ return t !== null && t.readyState === WebSocket.OPEN;
126
136
  }
127
137
  };
128
138
  }
129
139
  export {
130
- O as createBrowserClient
140
+ A as createBrowserClient
131
141
  };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createServer } from "http";
3
- import { WebSocketServer, WebSocket } from "ws";
3
+ import WebSocket$1, { WebSocketServer, WebSocket } from "ws";
4
4
  const DEFAULT_PATH = "/__twd/ws";
5
- function createTwdRelay(server2, options) {
5
+ function createTwdRelay(server, options) {
6
6
  const path = options?.path ?? DEFAULT_PATH;
7
7
  const onError = options?.onError;
8
8
  const wss = new WebSocketServer({ noServer: true });
@@ -140,13 +140,13 @@ function createTwdRelay(server2, options) {
140
140
  });
141
141
  }
142
142
  };
143
- server2.on("upgrade", upgradeHandler);
143
+ server.on("upgrade", upgradeHandler);
144
144
  wss.on("error", (err) => {
145
145
  if (onError) onError(err);
146
146
  });
147
147
  return {
148
148
  close() {
149
- server2.removeListener("upgrade", upgradeHandler);
149
+ server.removeListener("upgrade", upgradeHandler);
150
150
  for (const client of clients) {
151
151
  client.close(1e3, "Relay shutting down");
152
152
  }
@@ -166,34 +166,161 @@ function createTwdRelay(server2, options) {
166
166
  }
167
167
  };
168
168
  }
169
+ function run(options) {
170
+ const { port, timeout } = options;
171
+ const url = `ws://localhost:${port}/__twd/ws`;
172
+ console.log(`Connecting to ${url}...`);
173
+ const ws = new WebSocket$1(url);
174
+ let runSent = false;
175
+ let runComplete = false;
176
+ let failed = false;
177
+ const timer = setTimeout(() => {
178
+ console.error(`
179
+ Timeout: no run:complete received within ${timeout / 1e3}s`);
180
+ ws.close();
181
+ process.exit(1);
182
+ }, timeout);
183
+ ws.on("open", () => {
184
+ ws.send(JSON.stringify({ type: "hello", role: "client" }));
185
+ });
186
+ ws.on("message", (data) => {
187
+ const msg = JSON.parse(data.toString());
188
+ switch (msg.type) {
189
+ case "connected":
190
+ if (msg.browser && !runSent) {
191
+ runSent = true;
192
+ console.log("Browser connected, triggering test run...\n");
193
+ ws.send(JSON.stringify({ type: "run", scope: "all" }));
194
+ } else if (!msg.browser) {
195
+ console.log("Waiting for browser to connect...");
196
+ }
197
+ break;
198
+ case "run:start":
199
+ console.log(`Running ${msg.testCount} test(s)...
200
+ `);
201
+ break;
202
+ case "test:start":
203
+ console.log(` RUN: ${msg.suite} > ${msg.name}`);
204
+ break;
205
+ case "test:pass":
206
+ console.log(` PASS: ${msg.suite} > ${msg.name} (${msg.duration}ms)`);
207
+ break;
208
+ case "test:fail":
209
+ failed = true;
210
+ console.log(` FAIL: ${msg.suite} > ${msg.name} (${msg.duration}ms)`);
211
+ if (msg.error) {
212
+ console.log(` Error: ${msg.error}`);
213
+ }
214
+ break;
215
+ case "test:skip":
216
+ console.log(` SKIP: ${msg.suite} > ${msg.name}`);
217
+ break;
218
+ case "run:complete": {
219
+ const duration = (msg.duration / 1e3).toFixed(1);
220
+ console.log(`
221
+ --- Run complete ---`);
222
+ console.log(`Passed: ${msg.passed} | Failed: ${msg.failed} | Skipped: ${msg.skipped}`);
223
+ console.log(`Duration: ${duration}s`);
224
+ runComplete = true;
225
+ clearTimeout(timer);
226
+ ws.close();
227
+ process.exit(failed || msg.failed > 0 ? 1 : 0);
228
+ break;
229
+ }
230
+ case "error":
231
+ console.error(`Error [${msg.code}]: ${msg.message}`);
232
+ break;
233
+ }
234
+ });
235
+ ws.on("close", () => {
236
+ clearTimeout(timer);
237
+ if (!runComplete) {
238
+ console.error("Connection closed before run completed");
239
+ process.exit(1);
240
+ }
241
+ });
242
+ ws.on("error", (err) => {
243
+ clearTimeout(timer);
244
+ console.error(`Connection error: ${err.message}`);
245
+ process.exit(1);
246
+ });
247
+ }
169
248
  const args = process.argv.slice(2);
170
- let port = 9876;
171
- for (let i = 0; i < args.length; i++) {
172
- if (args[i] === "--port" && args[i + 1]) {
173
- port = parseInt(args[i + 1], 10);
249
+ const subcommand = args.find((a) => !a.startsWith("--"));
250
+ function parseFlag(name) {
251
+ const idx = args.indexOf(name);
252
+ if (idx !== -1 && args[idx + 1]) return args[idx + 1];
253
+ return void 0;
254
+ }
255
+ function printHelp() {
256
+ console.log(`Usage: twd-relay [command] [options]
257
+
258
+ Commands:
259
+ (none), serve Start the standalone relay server (default)
260
+ run Connect to a relay and trigger a test run
261
+
262
+ Options for serve:
263
+ --port <port> Port to listen on (default: 9876)
264
+
265
+ Options for run:
266
+ --port <port> Relay port to connect to (default: 5173)
267
+ --timeout <ms> Timeout in ms (default: 180000)
268
+
269
+ Examples:
270
+ twd-relay # start relay on port 9876
271
+ twd-relay run # trigger run via Vite dev server on 5173
272
+ twd-relay run --port 9876 # trigger run on custom port
273
+ twd-relay run --timeout 30000 # custom timeout`);
274
+ }
275
+ if (args.includes("--help") || args.includes("-h")) {
276
+ printHelp();
277
+ process.exit(0);
278
+ }
279
+ if (subcommand === "run") {
280
+ const portStr = parseFlag("--port");
281
+ const timeoutStr = parseFlag("--timeout");
282
+ const port = portStr ? parseInt(portStr, 10) : 5173;
283
+ if (isNaN(port)) {
284
+ console.error("Invalid port number:", portStr);
285
+ process.exit(1);
286
+ }
287
+ const timeout = timeoutStr ? parseInt(timeoutStr, 10) : 18e4;
288
+ if (isNaN(timeout)) {
289
+ console.error("Invalid timeout value:", timeoutStr);
290
+ process.exit(1);
291
+ }
292
+ run({ port, timeout });
293
+ } else if (!subcommand || subcommand === "serve") {
294
+ let shutdown = function() {
295
+ console.log("\nShutting down...");
296
+ relay.close();
297
+ server.close(() => {
298
+ process.exit(0);
299
+ });
300
+ };
301
+ const portStr = parseFlag("--port");
302
+ let port = 9876;
303
+ if (portStr) {
304
+ port = parseInt(portStr, 10);
174
305
  if (isNaN(port)) {
175
- console.error("Invalid port number:", args[i + 1]);
306
+ console.error("Invalid port number:", portStr);
176
307
  process.exit(1);
177
308
  }
178
- i++;
179
- }
180
- }
181
- const server = createServer();
182
- const relay = createTwdRelay(server, {
183
- onError(err) {
184
- console.error("[twd-relay] Error:", err.message);
185
309
  }
186
- });
187
- server.listen(port, () => {
188
- console.log(`TWD Relay running on ws://localhost:${port}/__twd/ws`);
189
- console.log("Waiting for connections...");
190
- });
191
- function shutdown() {
192
- console.log("\nShutting down...");
193
- relay.close();
194
- server.close(() => {
195
- process.exit(0);
310
+ const server = createServer();
311
+ const relay = createTwdRelay(server, {
312
+ onError(err) {
313
+ console.error("[twd-relay] Error:", err.message);
314
+ }
315
+ });
316
+ server.listen(port, () => {
317
+ console.log(`TWD Relay running on ws://localhost:${port}/__twd/ws`);
318
+ console.log("Waiting for connections...");
196
319
  });
320
+ process.on("SIGINT", shutdown);
321
+ process.on("SIGTERM", shutdown);
322
+ } else {
323
+ console.error(`Unknown command: ${subcommand}`);
324
+ printHelp();
325
+ process.exit(1);
197
326
  }
198
- process.on("SIGINT", shutdown);
199
- process.on("SIGTERM", shutdown);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twd-relay",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "WebSocket relay for TWD — enables AI agents and external tools to run and observe in-browser tests",
5
5
  "license": "MIT",
6
6
  "author": "BRIKEV",
@@ -44,7 +44,7 @@
44
44
  "build": "vite build && vite build -c vite.cli.config.ts && node -e \"const fs=require('fs');const f='dist/cli.js';fs.writeFileSync(f,'#!/usr/bin/env node\\n'+fs.readFileSync(f,'utf8'))\" && chmod +x dist/cli.js",
45
45
  "dev": "node dist/cli.js",
46
46
  "relay": "npm run build && node dist/cli.js",
47
- "send-run": "node scripts/send-run.js",
47
+ "send-run": "node dist/cli.js run",
48
48
  "test": "vitest",
49
49
  "test:ci": "vitest --run --coverage"
50
50
  },