sentinelayer-cli 0.30.13 → 0.30.14

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.
@@ -0,0 +1,181 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import util from "node:util";
5
+
6
+ export const DEFAULT_ROTATING_LOG_MAX_BYTES = 5 * 1024 * 1024;
7
+ export const DEFAULT_ROTATING_LOG_MAX_FILES = 5;
8
+
9
+ function normalizeString(value) {
10
+ return String(value || "").trim();
11
+ }
12
+
13
+ function normalizePositiveInteger(value, fallbackValue) {
14
+ const numeric = Number(value);
15
+ if (!Number.isFinite(numeric) || numeric <= 0) {
16
+ return fallbackValue;
17
+ }
18
+ return Math.max(1, Math.floor(numeric));
19
+ }
20
+
21
+ export function normalizeRotatingLogOptions({
22
+ maxBytes = DEFAULT_ROTATING_LOG_MAX_BYTES,
23
+ maxFiles = DEFAULT_ROTATING_LOG_MAX_FILES,
24
+ } = {}) {
25
+ return {
26
+ maxBytes: normalizePositiveInteger(maxBytes, DEFAULT_ROTATING_LOG_MAX_BYTES),
27
+ maxFiles: normalizePositiveInteger(maxFiles, DEFAULT_ROTATING_LOG_MAX_FILES),
28
+ };
29
+ }
30
+
31
+ export function rotateLogFileIfNeeded(logPath, options = {}) {
32
+ const normalizedLogPath = normalizeString(logPath);
33
+ if (!normalizedLogPath) {
34
+ return { rotated: false, reason: "missing_log_path" };
35
+ }
36
+
37
+ const { maxBytes, maxFiles } = normalizeRotatingLogOptions(options);
38
+ let currentSize = 0;
39
+ try {
40
+ currentSize = fs.statSync(normalizedLogPath).size;
41
+ } catch (error) {
42
+ if (error?.code === "ENOENT") {
43
+ return { rotated: false, reason: "missing_file", maxBytes, maxFiles };
44
+ }
45
+ throw error;
46
+ }
47
+
48
+ if (currentSize < maxBytes) {
49
+ return { rotated: false, reason: "under_limit", size: currentSize, maxBytes, maxFiles };
50
+ }
51
+
52
+ fs.mkdirSync(path.dirname(normalizedLogPath), { recursive: true });
53
+ const backupCount = Math.max(0, maxFiles - 1);
54
+ if (backupCount === 0) {
55
+ fs.rmSync(normalizedLogPath, { force: true });
56
+ return { rotated: true, reason: "removed_active", size: currentSize, maxBytes, maxFiles };
57
+ }
58
+
59
+ fs.rmSync(`${normalizedLogPath}.${backupCount}`, { force: true });
60
+ for (let index = backupCount - 1; index >= 1; index -= 1) {
61
+ const from = `${normalizedLogPath}.${index}`;
62
+ const to = `${normalizedLogPath}.${index + 1}`;
63
+ if (fs.existsSync(from)) {
64
+ fs.renameSync(from, to);
65
+ }
66
+ }
67
+ fs.renameSync(normalizedLogPath, `${normalizedLogPath}.1`);
68
+ return { rotated: true, reason: "rotated", size: currentSize, maxBytes, maxFiles };
69
+ }
70
+
71
+ export function appendRotatingLogLine(logPath, line = "", options = {}) {
72
+ const normalizedLogPath = normalizeString(logPath);
73
+ if (!normalizedLogPath) {
74
+ return { written: false, reason: "missing_log_path" };
75
+ }
76
+ fs.mkdirSync(path.dirname(normalizedLogPath), { recursive: true });
77
+ const rotation = rotateLogFileIfNeeded(normalizedLogPath, options);
78
+ fs.appendFileSync(normalizedLogPath, `${redactLogSecrets(line)}\n`, "utf-8");
79
+ return { written: true, rotation };
80
+ }
81
+
82
+ function normalizeLogChunk(chunk, encoding = "utf8") {
83
+ if (Buffer.isBuffer(chunk)) {
84
+ return chunk.toString(typeof encoding === "string" ? encoding : "utf8");
85
+ }
86
+ return String(chunk ?? "");
87
+ }
88
+
89
+ export function formatConsoleLogLine(parts = []) {
90
+ return parts.map((part) => (typeof part === "string" ? part : util.inspect(part))).join(" ");
91
+ }
92
+
93
+ export function redactLogSecrets(value = "") {
94
+ return String(value ?? "")
95
+ .replace(/\bbearer\s+[a-z0-9._~+/=-]+\b/gi, "bearer [REDACTED]")
96
+ .replace(
97
+ /\b([a-z0-9_.-]*(?:token|secret|password|api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token)[a-z0-9_.-]*)\b\s*[:=]\s*["']?[^"'\s,;]+["']?/gi,
98
+ "$1=[REDACTED]",
99
+ )
100
+ .replace(/\beyJ[a-z0-9_-]{8,}\.[a-z0-9_-]{10,}\.[a-z0-9_-]{10,}\b/gi, "[REDACTED_JWT]");
101
+ }
102
+
103
+ export function installRotatingConsoleLog({
104
+ logPath,
105
+ maxBytes = DEFAULT_ROTATING_LOG_MAX_BYTES,
106
+ maxFiles = DEFAULT_ROTATING_LOG_MAX_FILES,
107
+ tee = true,
108
+ now = () => new Date(),
109
+ onError = null,
110
+ captureConsoleError = true,
111
+ captureStderr = true,
112
+ } = {}) {
113
+ const normalizedLogPath = normalizeString(logPath);
114
+ if (!normalizedLogPath) {
115
+ return () => {};
116
+ }
117
+
118
+ const options = normalizeRotatingLogOptions({ maxBytes, maxFiles });
119
+ const originalLog = console.log;
120
+ const originalError = console.error;
121
+ const originalStderrWrite = process.stderr.write;
122
+ let warned = false;
123
+ const writeLogLine = (line) => {
124
+ try {
125
+ appendRotatingLogLine(
126
+ normalizedLogPath,
127
+ `${now().toISOString()} ${line}`,
128
+ options,
129
+ );
130
+ } catch (error) {
131
+ if (!warned) {
132
+ warned = true;
133
+ if (typeof onError === "function") {
134
+ onError(error);
135
+ }
136
+ }
137
+ }
138
+ };
139
+
140
+ console.log = (...parts) => {
141
+ if (tee) {
142
+ originalLog(...parts);
143
+ }
144
+ writeLogLine(formatConsoleLogLine(parts));
145
+ };
146
+
147
+ if (captureStderr) {
148
+ process.stderr.write = function rotatingStderrWrite(chunk, encoding, callback) {
149
+ const text = normalizeLogChunk(chunk, encoding).replace(/\s+$/u, "");
150
+ if (text) {
151
+ writeLogLine(`[stderr] ${text}`);
152
+ }
153
+ if (!tee) {
154
+ if (typeof encoding === "function") {
155
+ encoding();
156
+ } else if (typeof callback === "function") {
157
+ callback();
158
+ }
159
+ return true;
160
+ }
161
+ return originalStderrWrite.apply(process.stderr, arguments);
162
+ };
163
+ }
164
+
165
+ if (captureConsoleError) {
166
+ console.error = (...parts) => {
167
+ if (tee) {
168
+ originalError(...parts);
169
+ }
170
+ if (!captureStderr || !tee) {
171
+ writeLogLine(`[stderr] ${formatConsoleLogLine(parts)}`);
172
+ }
173
+ };
174
+ }
175
+
176
+ return () => {
177
+ console.log = originalLog;
178
+ console.error = originalError;
179
+ process.stderr.write = originalStderrWrite;
180
+ };
181
+ }
@@ -461,7 +461,13 @@ function abortRequestController(controller, reason) {
461
461
  function cancelResponseBody(response) {
462
462
  try {
463
463
  if (response?.body && typeof response.body.cancel === "function") {
464
- void response.body.cancel();
464
+ if (response.body.locked) return;
465
+ const cancellation = response.body.cancel();
466
+ if (cancellation && typeof cancellation.catch === "function") {
467
+ void cancellation.catch(() => {
468
+ // Best-effort socket/body cleanup only.
469
+ });
470
+ }
465
471
  }
466
472
  } catch {
467
473
  // Best-effort socket/body cleanup only.