coderail-watch 0.1.1 → 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.
- package/README.md +12 -0
- package/bin/coderail-watch.js +102 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,3 +15,15 @@ cd tools/coderail-watch
|
|
|
15
15
|
npm login
|
|
16
16
|
npm run publish:public
|
|
17
17
|
```
|
|
18
|
+
|
|
19
|
+
If you are fixing a published bug, bump the version before publishing:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd tools/coderail-watch
|
|
23
|
+
npm version patch
|
|
24
|
+
npm run publish:public
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2FA note:
|
|
28
|
+
|
|
29
|
+
- npm no longer allows adding new TOTP 2FA from the CLI. Enable 2FA with a security key at https://npmjs.com/settings/<your-username>/tfa before publishing.
|
package/bin/coderail-watch.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
133
|
+
attachHandler(socket, "close", (event) => {
|
|
79
134
|
isOpen = false;
|
|
80
|
-
|
|
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
|
|
85
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|