coderail-watch 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/bin/coderail-watch.js +102 -12
  2. package/package.json +1 -1
@@ -2,10 +2,13 @@
2
2
  "use strict";
3
3
 
4
4
  const readline = require("readline");
5
- const WebSocket = require("ws");
6
5
 
7
6
  const DEFAULT_BASE_URL = "http://localhost:8000";
8
7
  const DEFAULT_RETRY_WAIT_MS = 2000;
8
+ const ALERT_PREFIX = "[[CODERAIL_ERROR]] ";
9
+ const ERROR_PATTERN = /(^|[\s:])(?:error|exception|traceback|panic|fatal)(?=[\s:])/i;
10
+
11
+ const isErrorLine = (line) => ERROR_PATTERN.test(line);
9
12
 
10
13
  const parseArgs = (argv) => {
11
14
  const args = {};
@@ -28,6 +31,19 @@ const log = (message) => {
28
31
  process.stderr.write(`[coderail-watch] ${message}\n`);
29
32
  };
30
33
 
34
+ let WebSocketImpl = globalThis.WebSocket;
35
+ if (!WebSocketImpl) {
36
+ try {
37
+ WebSocketImpl = require("ws");
38
+ } catch (error) {
39
+ log(
40
+ "Missing dependency 'ws'. Run `cd tools/coderail-watch && npm install` or use the published package via `npx coderail-watch@latest ...`.",
41
+ );
42
+ process.exit(1);
43
+ }
44
+ }
45
+ const WebSocket = WebSocketImpl;
46
+
31
47
  const buildWsUrl = (baseUrl, sessionId, projectKey, token) => {
32
48
  let normalized = baseUrl.trim();
33
49
  if (!normalized.startsWith("http://") && !normalized.startsWith("https://")) {
@@ -62,27 +78,84 @@ const wsUrl = buildWsUrl(baseUrl, sessionId, projectKey, token);
62
78
  let socket = null;
63
79
  let isOpen = false;
64
80
  const queue = [];
81
+ let retryCount = 0;
82
+ let lastErrorMessage = "";
83
+
84
+ const attachHandler = (target, event, handler) => {
85
+ if (typeof target.on === "function") {
86
+ target.on(event, handler);
87
+ return;
88
+ }
89
+ if (typeof target.addEventListener === "function") {
90
+ target.addEventListener(event, handler);
91
+ }
92
+ };
93
+
94
+ const isSocketOpen = () => {
95
+ if (!socket) return false;
96
+ if (typeof WebSocket.OPEN === "number") {
97
+ return socket.readyState === WebSocket.OPEN;
98
+ }
99
+ return isOpen;
100
+ };
65
101
 
66
102
  const connect = () => {
67
- socket = new WebSocket(wsUrl);
103
+ retryCount += 1;
104
+ log(`connecting (#${retryCount})...`);
105
+ try {
106
+ socket = new WebSocket(wsUrl);
107
+ } catch (error) {
108
+ lastErrorMessage = String(error);
109
+ log(`connect failed: ${lastErrorMessage}`);
110
+ setTimeout(connect, retryWaitMs);
111
+ return;
112
+ }
68
113
 
69
- socket.on("open", () => {
114
+ attachHandler(socket, "open", () => {
70
115
  isOpen = true;
116
+ retryCount = 0;
71
117
  log(`connected: ${wsUrl}`);
72
118
  while (queue.length) {
73
119
  const payload = queue.shift();
74
- socket.send(payload);
120
+ if (!isSocketOpen()) {
121
+ queue.unshift(payload);
122
+ break;
123
+ }
124
+ try {
125
+ socket.send(payload);
126
+ } catch (error) {
127
+ queue.unshift(payload);
128
+ break;
129
+ }
75
130
  }
76
131
  });
77
132
 
78
- socket.on("close", () => {
133
+ attachHandler(socket, "close", (event) => {
79
134
  isOpen = false;
80
- log("disconnected; retrying...");
135
+ const code =
136
+ event && typeof event === "object" && "code" in event ? event.code : null;
137
+ const reason =
138
+ event && typeof event === "object" && "reason" in event
139
+ ? event.reason
140
+ : "";
141
+ const detail = [code ? `code=${code}` : null, reason || null]
142
+ .filter(Boolean)
143
+ .join(" ");
144
+ log(
145
+ `disconnected; retrying in ${retryWaitMs}ms${
146
+ detail ? ` (${detail})` : ""
147
+ }`,
148
+ );
81
149
  setTimeout(connect, retryWaitMs);
82
150
  });
83
151
 
84
- socket.on("error", (error) => {
85
- log(`error: ${error.message}`);
152
+ attachHandler(socket, "error", (error) => {
153
+ const message =
154
+ error && typeof error === "object" && "message" in error
155
+ ? error.message
156
+ : String(error);
157
+ lastErrorMessage = message;
158
+ log(`error: ${message}`);
86
159
  });
87
160
  };
88
161
 
@@ -93,16 +166,33 @@ const rl = readline.createInterface({
93
166
  crlfDelay: Infinity,
94
167
  });
95
168
 
169
+ const enqueueOrSend = (payload) => {
170
+ if (isSocketOpen()) {
171
+ try {
172
+ socket.send(payload);
173
+ return;
174
+ } catch (error) {
175
+ queue.push(payload);
176
+ return;
177
+ }
178
+ }
179
+ queue.push(payload);
180
+ };
181
+
96
182
  rl.on("line", (line) => {
97
183
  const payload = JSON.stringify({
98
184
  type: "log",
99
185
  content: line,
100
186
  timestamp: new Date().toISOString(),
101
187
  });
102
- if (isOpen && socket) {
103
- socket.send(payload);
104
- } else {
105
- queue.push(payload);
188
+ enqueueOrSend(payload);
189
+ if (isErrorLine(line)) {
190
+ const alertPayload = JSON.stringify({
191
+ type: "alert",
192
+ content: `${ALERT_PREFIX}${line}`,
193
+ timestamp: new Date().toISOString(),
194
+ });
195
+ enqueueOrSend(alertPayload);
106
196
  }
107
197
  });
108
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderail-watch",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Stream terminal output to CodeRail backend over WebSocket.",
5
5
  "bin": {
6
6
  "coderail-watch": "bin/coderail-watch.js"