coderail-watch 0.1.3 → 0.1.5

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 CHANGED
@@ -8,6 +8,18 @@ Usage:
8
8
  npx coderail-watch --session-id <session_id> --project-key <project_public_id> --base-url http://localhost:8000
9
9
  ```
10
10
 
11
+ Switch base URL by env:
12
+
13
+ ```bash
14
+ npx coderail-watch --session-id <session_id> --project-key <project_public_id> --env staging
15
+ ```
16
+
17
+ Watch a log file instead of stdin:
18
+
19
+ ```bash
20
+ npx coderail-watch --session-id <session_id> --project-key <project_public_id> --base-url http://localhost:8000 --log-path /path/to/logfile
21
+ ```
22
+
11
23
  Publish (npm):
12
24
 
13
25
  ```bash
@@ -1,9 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ const fs = require("fs");
4
5
  const readline = require("readline");
5
6
 
6
7
  const DEFAULT_BASE_URL = "http://localhost:8000";
8
+ const ENV_BASE_URLS = {
9
+ local: "http://localhost:8000",
10
+ staging: "https://api.dev-coderail.local",
11
+ production: "https://api.coderail.local",
12
+ };
7
13
  const DEFAULT_RETRY_WAIT_MS = 2000;
8
14
  const ALERT_PREFIX = "[[CODERAIL_ERROR]] ";
9
15
  const ERROR_PATTERN = /(^|[\s:])(?:error|exception|traceback|panic|fatal)(?=[\s:])/i;
@@ -65,15 +71,26 @@ const buildWsUrl = (baseUrl, sessionId, projectKey, token) => {
65
71
  const args = parseArgs(process.argv.slice(2));
66
72
  const sessionId = args["session-id"];
67
73
  const projectKey = args["project-key"];
68
- const baseUrl = args["base-url"] || DEFAULT_BASE_URL;
74
+ const env = args["env"];
75
+ const baseUrl =
76
+ args["base-url"] ||
77
+ (env && ENV_BASE_URLS[env] ? ENV_BASE_URLS[env] : DEFAULT_BASE_URL);
69
78
  const token = args["token"];
70
79
  const retryWaitMs = Number(args["retry-wait"] || DEFAULT_RETRY_WAIT_MS);
80
+ const logPath = args["log-path"];
71
81
 
72
82
  if (!sessionId || !projectKey) {
73
83
  log("Missing required args: --session-id and --project-key");
74
84
  process.exit(1);
75
85
  }
76
86
 
87
+ if (env && !ENV_BASE_URLS[env]) {
88
+ log(
89
+ `Unknown env '${env}'. Use one of: ${Object.keys(ENV_BASE_URLS).join(", ")}`,
90
+ );
91
+ process.exit(1);
92
+ }
93
+
77
94
  const wsUrl = buildWsUrl(baseUrl, sessionId, projectKey, token);
78
95
  let socket = null;
79
96
  let isOpen = false;
@@ -179,7 +196,7 @@ const enqueueOrSend = (payload) => {
179
196
  queue.push(payload);
180
197
  };
181
198
 
182
- rl.on("line", (line) => {
199
+ const handleLine = (line) => {
183
200
  const payload = JSON.stringify({
184
201
  type: "log",
185
202
  content: line,
@@ -194,10 +211,105 @@ rl.on("line", (line) => {
194
211
  });
195
212
  enqueueOrSend(alertPayload);
196
213
  }
197
- });
214
+ };
215
+
216
+ const startFileTailer = (targetPath) => {
217
+ let fileOffset = 0;
218
+ let remainder = "";
219
+
220
+ const initializeOffset = () => {
221
+ try {
222
+ const stats = fs.statSync(targetPath);
223
+ fileOffset = stats.size;
224
+ return true;
225
+ } catch (error) {
226
+ log(`log-path not found: ${targetPath} (retrying...)`);
227
+ return false;
228
+ }
229
+ };
230
+
231
+ const flushChunk = (chunk) => {
232
+ const text = `${remainder}${chunk}`;
233
+ const lines = text.split(/\r?\n/);
234
+ remainder = lines.pop() ?? "";
235
+ lines.forEach((line) => {
236
+ if (line.length > 0) {
237
+ handleLine(line);
238
+ }
239
+ });
240
+ };
241
+
242
+ const readNewData = () => {
243
+ fs.stat(targetPath, (error, stats) => {
244
+ if (error) {
245
+ log(`log-path read error: ${error.message || String(error)}`);
246
+ return;
247
+ }
248
+ if (stats.size < fileOffset) {
249
+ fileOffset = 0;
250
+ }
251
+ if (stats.size === fileOffset) {
252
+ return;
253
+ }
254
+ const stream = fs.createReadStream(targetPath, {
255
+ start: fileOffset,
256
+ end: stats.size - 1,
257
+ encoding: "utf8",
258
+ });
259
+ stream.on("data", flushChunk);
260
+ stream.on("end", () => {
261
+ fileOffset = stats.size;
262
+ });
263
+ stream.on("error", (streamError) => {
264
+ log(
265
+ `log-path stream error: ${
266
+ streamError.message || String(streamError)
267
+ }`,
268
+ );
269
+ });
270
+ });
271
+ };
272
+
273
+ const startWatcher = () => {
274
+ try {
275
+ fs.watch(targetPath, { persistent: true }, (eventType) => {
276
+ if (eventType === "change") {
277
+ readNewData();
278
+ }
279
+ });
280
+ } catch (error) {
281
+ log(`log-path watch error: ${error.message || String(error)}`);
282
+ }
283
+ };
198
284
 
199
- rl.on("close", () => {
200
- if (socket) {
201
- socket.close();
285
+ if (!initializeOffset()) {
286
+ const retryTimer = setInterval(() => {
287
+ if (initializeOffset()) {
288
+ clearInterval(retryTimer);
289
+ startWatcher();
290
+ }
291
+ }, retryWaitMs);
292
+ return;
202
293
  }
203
- });
294
+
295
+ startWatcher();
296
+ };
297
+
298
+ if (logPath) {
299
+ startFileTailer(logPath);
300
+ } else {
301
+ const rl = readline.createInterface({
302
+ input: process.stdin,
303
+ crlfDelay: Infinity,
304
+ });
305
+
306
+ rl.on("line", (line) => {
307
+ handleLine(line);
308
+ });
309
+
310
+ rl.on("close", () => {
311
+ if (socket) {
312
+ socket.close();
313
+ }
314
+ });
315
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderail-watch",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Stream terminal output to CodeRail backend over WebSocket.",
5
5
  "bin": {
6
6
  "coderail-watch": "bin/coderail-watch.js"