lifecycleion 0.0.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.
- package/LICENSE +22 -0
- package/README.md +125 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/arrays.cjs +95 -0
- package/dist/lib/arrays.cjs.map +1 -0
- package/dist/lib/arrays.d.cts +15 -0
- package/dist/lib/arrays.d.ts +15 -0
- package/dist/lib/arrays.js +63 -0
- package/dist/lib/arrays.js.map +1 -0
- package/dist/lib/ascii-tables/index.cjs +642 -0
- package/dist/lib/ascii-tables/index.cjs.map +1 -0
- package/dist/lib/ascii-tables/index.d.cts +66 -0
- package/dist/lib/ascii-tables/index.d.ts +66 -0
- package/dist/lib/ascii-tables/index.js +603 -0
- package/dist/lib/ascii-tables/index.js.map +1 -0
- package/dist/lib/clamp.cjs +41 -0
- package/dist/lib/clamp.cjs.map +1 -0
- package/dist/lib/clamp.d.cts +26 -0
- package/dist/lib/clamp.d.ts +26 -0
- package/dist/lib/clamp.js +15 -0
- package/dist/lib/clamp.js.map +1 -0
- package/dist/lib/constants.cjs +73 -0
- package/dist/lib/constants.cjs.map +1 -0
- package/dist/lib/constants.d.cts +17 -0
- package/dist/lib/constants.d.ts +17 -0
- package/dist/lib/constants.js +34 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/curly-brackets.cjs +77 -0
- package/dist/lib/curly-brackets.cjs.map +1 -0
- package/dist/lib/curly-brackets.d.cts +17 -0
- package/dist/lib/curly-brackets.d.ts +17 -0
- package/dist/lib/curly-brackets.js +52 -0
- package/dist/lib/curly-brackets.js.map +1 -0
- package/dist/lib/deep-clone.cjs +87 -0
- package/dist/lib/deep-clone.cjs.map +1 -0
- package/dist/lib/deep-clone.d.cts +19 -0
- package/dist/lib/deep-clone.d.ts +19 -0
- package/dist/lib/deep-clone.js +62 -0
- package/dist/lib/deep-clone.js.map +1 -0
- package/dist/lib/error-to-string.cjs +743 -0
- package/dist/lib/error-to-string.cjs.map +1 -0
- package/dist/lib/error-to-string.d.cts +3 -0
- package/dist/lib/error-to-string.d.ts +3 -0
- package/dist/lib/error-to-string.js +706 -0
- package/dist/lib/error-to-string.js.map +1 -0
- package/dist/lib/event-emitter.cjs +899 -0
- package/dist/lib/event-emitter.cjs.map +1 -0
- package/dist/lib/event-emitter.d.cts +78 -0
- package/dist/lib/event-emitter.d.ts +78 -0
- package/dist/lib/event-emitter.js +861 -0
- package/dist/lib/event-emitter.js.map +1 -0
- package/dist/lib/id-helpers.cjs +205 -0
- package/dist/lib/id-helpers.cjs.map +1 -0
- package/dist/lib/id-helpers.d.cts +198 -0
- package/dist/lib/id-helpers.d.ts +198 -0
- package/dist/lib/id-helpers.js +170 -0
- package/dist/lib/id-helpers.js.map +1 -0
- package/dist/lib/is-boolean.cjs +33 -0
- package/dist/lib/is-boolean.cjs.map +1 -0
- package/dist/lib/is-boolean.d.cts +19 -0
- package/dist/lib/is-boolean.d.ts +19 -0
- package/dist/lib/is-boolean.js +8 -0
- package/dist/lib/is-boolean.js.map +1 -0
- package/dist/lib/is-function.cjs +33 -0
- package/dist/lib/is-function.cjs.map +1 -0
- package/dist/lib/is-function.d.cts +3 -0
- package/dist/lib/is-function.d.ts +3 -0
- package/dist/lib/is-function.js +8 -0
- package/dist/lib/is-function.js.map +1 -0
- package/dist/lib/is-number.cjs +38 -0
- package/dist/lib/is-number.cjs.map +1 -0
- package/dist/lib/is-number.d.cts +38 -0
- package/dist/lib/is-number.d.ts +38 -0
- package/dist/lib/is-number.js +12 -0
- package/dist/lib/is-number.js.map +1 -0
- package/dist/lib/is-plain-object.cjs +33 -0
- package/dist/lib/is-plain-object.cjs.map +1 -0
- package/dist/lib/is-plain-object.d.cts +20 -0
- package/dist/lib/is-plain-object.d.ts +20 -0
- package/dist/lib/is-plain-object.js +8 -0
- package/dist/lib/is-plain-object.js.map +1 -0
- package/dist/lib/is-promise.cjs +34 -0
- package/dist/lib/is-promise.cjs.map +1 -0
- package/dist/lib/is-promise.d.cts +3 -0
- package/dist/lib/is-promise.d.ts +3 -0
- package/dist/lib/is-promise.js +9 -0
- package/dist/lib/is-promise.js.map +1 -0
- package/dist/lib/json-helpers.cjs +49 -0
- package/dist/lib/json-helpers.cjs.map +1 -0
- package/dist/lib/json-helpers.d.cts +10 -0
- package/dist/lib/json-helpers.d.ts +10 -0
- package/dist/lib/json-helpers.js +22 -0
- package/dist/lib/json-helpers.js.map +1 -0
- package/dist/lib/lifecycle-manager/index.cjs +5594 -0
- package/dist/lib/lifecycle-manager/index.cjs.map +1 -0
- package/dist/lib/lifecycle-manager/index.d.cts +2044 -0
- package/dist/lib/lifecycle-manager/index.d.ts +2044 -0
- package/dist/lib/lifecycle-manager/index.js +5543 -0
- package/dist/lib/lifecycle-manager/index.js.map +1 -0
- package/dist/lib/logger/index.cjs +2514 -0
- package/dist/lib/logger/index.cjs.map +1 -0
- package/dist/lib/logger/index.d.cts +630 -0
- package/dist/lib/logger/index.d.ts +630 -0
- package/dist/lib/logger/index.js +2470 -0
- package/dist/lib/logger/index.js.map +1 -0
- package/dist/lib/padding-utils.cjs +77 -0
- package/dist/lib/padding-utils.cjs.map +1 -0
- package/dist/lib/padding-utils.d.cts +44 -0
- package/dist/lib/padding-utils.d.ts +44 -0
- package/dist/lib/padding-utils.js +46 -0
- package/dist/lib/padding-utils.js.map +1 -0
- package/dist/lib/process-signal-manager.cjs +1306 -0
- package/dist/lib/process-signal-manager.cjs.map +1 -0
- package/dist/lib/process-signal-manager.d.cts +305 -0
- package/dist/lib/process-signal-manager.d.ts +305 -0
- package/dist/lib/process-signal-manager.js +1269 -0
- package/dist/lib/process-signal-manager.js.map +1 -0
- package/dist/lib/promise-protected-resolver.cjs +828 -0
- package/dist/lib/promise-protected-resolver.cjs.map +1 -0
- package/dist/lib/promise-protected-resolver.d.cts +17 -0
- package/dist/lib/promise-protected-resolver.d.ts +17 -0
- package/dist/lib/promise-protected-resolver.js +791 -0
- package/dist/lib/promise-protected-resolver.js.map +1 -0
- package/dist/lib/retry-utils/index.cjs +2183 -0
- package/dist/lib/retry-utils/index.cjs.map +1 -0
- package/dist/lib/retry-utils/index.d.cts +321 -0
- package/dist/lib/retry-utils/index.d.ts +321 -0
- package/dist/lib/retry-utils/index.js +2133 -0
- package/dist/lib/retry-utils/index.js.map +1 -0
- package/dist/lib/safe-handle-callback.cjs +818 -0
- package/dist/lib/safe-handle-callback.cjs.map +1 -0
- package/dist/lib/safe-handle-callback.d.cts +43 -0
- package/dist/lib/safe-handle-callback.d.ts +43 -0
- package/dist/lib/safe-handle-callback.js +780 -0
- package/dist/lib/safe-handle-callback.js.map +1 -0
- package/dist/lib/serialize-error/index.cjs +93 -0
- package/dist/lib/serialize-error/index.cjs.map +1 -0
- package/dist/lib/serialize-error/index.d.cts +26 -0
- package/dist/lib/serialize-error/index.d.ts +26 -0
- package/dist/lib/serialize-error/index.js +64 -0
- package/dist/lib/serialize-error/index.js.map +1 -0
- package/dist/lib/single-event-observer.cjs +841 -0
- package/dist/lib/single-event-observer.cjs.map +1 -0
- package/dist/lib/single-event-observer.d.cts +54 -0
- package/dist/lib/single-event-observer.d.ts +54 -0
- package/dist/lib/single-event-observer.js +803 -0
- package/dist/lib/single-event-observer.js.map +1 -0
- package/dist/lib/sleep.cjs +37 -0
- package/dist/lib/sleep.cjs.map +1 -0
- package/dist/lib/sleep.d.cts +11 -0
- package/dist/lib/sleep.d.ts +11 -0
- package/dist/lib/sleep.js +12 -0
- package/dist/lib/sleep.js.map +1 -0
- package/dist/lib/strings.cjs +186 -0
- package/dist/lib/strings.cjs.map +1 -0
- package/dist/lib/strings.d.cts +107 -0
- package/dist/lib/strings.d.ts +107 -0
- package/dist/lib/strings.js +149 -0
- package/dist/lib/strings.js.map +1 -0
- package/dist/lib/tmp-dir.cjs +254 -0
- package/dist/lib/tmp-dir.cjs.map +1 -0
- package/dist/lib/tmp-dir.d.cts +63 -0
- package/dist/lib/tmp-dir.d.ts +63 -0
- package/dist/lib/tmp-dir.js +211 -0
- package/dist/lib/tmp-dir.js.map +1 -0
- package/dist/lib/unix-time-helpers.cjs +53 -0
- package/dist/lib/unix-time-helpers.cjs.map +1 -0
- package/dist/lib/unix-time-helpers.d.cts +56 -0
- package/dist/lib/unix-time-helpers.d.ts +56 -0
- package/dist/lib/unix-time-helpers.js +24 -0
- package/dist/lib/unix-time-helpers.js.map +1 -0
- package/package.json +220 -0
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
// src/lib/strings.ts
|
|
2
|
+
function isString(value) {
|
|
3
|
+
return typeof value === "string";
|
|
4
|
+
}
|
|
5
|
+
function splitGraphemes(text) {
|
|
6
|
+
const graphemes = [];
|
|
7
|
+
let grapheme = "";
|
|
8
|
+
let zwjSequence = "";
|
|
9
|
+
for (let i = 0; i < text.length; i++) {
|
|
10
|
+
const char = text[i];
|
|
11
|
+
const nextChar = text[i + 1] || "";
|
|
12
|
+
const code = char.charCodeAt(0);
|
|
13
|
+
if (code >= 768 && code <= 879 || // Combining Diacritical Marks
|
|
14
|
+
code >= 6832 && code <= 6911 || // Combining Diacritical Marks Extended
|
|
15
|
+
code >= 7616 && code <= 7679 || // Combining Diacritical Marks Supplement
|
|
16
|
+
code >= 65056 && code <= 65071 || // Combining Half Marks
|
|
17
|
+
code >= 3633 && code <= 3642 || // Thai combining marks
|
|
18
|
+
code >= 3655 && code <= 3662) {
|
|
19
|
+
grapheme += char;
|
|
20
|
+
} else if (char === "\u200D") {
|
|
21
|
+
zwjSequence += grapheme + char;
|
|
22
|
+
grapheme = "";
|
|
23
|
+
} else {
|
|
24
|
+
if (grapheme) {
|
|
25
|
+
if (zwjSequence) {
|
|
26
|
+
graphemes.push(zwjSequence + grapheme);
|
|
27
|
+
zwjSequence = "";
|
|
28
|
+
} else {
|
|
29
|
+
graphemes.push(grapheme);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
grapheme = char;
|
|
33
|
+
if (char >= "\uD800" && char <= "\uDBFF" && nextChar >= "\uDC00" && nextChar <= "\uDFFF") {
|
|
34
|
+
grapheme += nextChar;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (grapheme) {
|
|
40
|
+
if (zwjSequence) {
|
|
41
|
+
graphemes.push(zwjSequence + grapheme);
|
|
42
|
+
} else {
|
|
43
|
+
graphemes.push(grapheme);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return graphemes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/lib/constants.ts
|
|
50
|
+
var BLANK_SPACE = " ";
|
|
51
|
+
var EOL = "\n";
|
|
52
|
+
var DOUBLE_EOL = EOL + EOL;
|
|
53
|
+
var INDENT = " ".repeat(4);
|
|
54
|
+
var DOUBLE_INDENT = INDENT + INDENT;
|
|
55
|
+
var ASCII_LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
|
|
56
|
+
var ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
57
|
+
var ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE;
|
|
58
|
+
var DIGITS = "0123456789";
|
|
59
|
+
var HEX_DIGITS = DIGITS + "abcdefABCDEF";
|
|
60
|
+
var PUNCTUATION = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
|
61
|
+
var WHITESPACE = " \n\r\v\f";
|
|
62
|
+
var PRINTABLE = DIGITS + ASCII_LETTERS + PUNCTUATION + WHITESPACE;
|
|
63
|
+
|
|
64
|
+
// src/lib/padding-utils.ts
|
|
65
|
+
function padLeft(str, length, padStr = BLANK_SPACE) {
|
|
66
|
+
return str.padStart(length, padStr);
|
|
67
|
+
}
|
|
68
|
+
function padRight(str, length, padStr = BLANK_SPACE) {
|
|
69
|
+
return str.padEnd(length, padStr);
|
|
70
|
+
}
|
|
71
|
+
function padCenter(str, length, prefer = "left", padStr = BLANK_SPACE) {
|
|
72
|
+
const midStrLength = length - str.length;
|
|
73
|
+
if (midStrLength > 0) {
|
|
74
|
+
const padLeftAmount = prefer === "left" ? Math.ceil(midStrLength / 2) : Math.floor(midStrLength / 2);
|
|
75
|
+
const padRightAmount = prefer === "left" ? Math.floor(midStrLength / 2) : Math.ceil(midStrLength / 2);
|
|
76
|
+
return padLeft("", padLeftAmount, padStr) + str + padRight("", padRightAmount, padStr);
|
|
77
|
+
} else {
|
|
78
|
+
return str;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function padCenterPreferRight(str, length, padStr = BLANK_SPACE) {
|
|
82
|
+
return padCenter(str, length, "right", padStr);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/lib/ascii-tables/ascii-table-utils.ts
|
|
86
|
+
import stringWidth from "string-width";
|
|
87
|
+
var ASCIITableUtils = class _ASCIITableUtils {
|
|
88
|
+
static centerText(text, width) {
|
|
89
|
+
return padCenterPreferRight(text, width, " ");
|
|
90
|
+
}
|
|
91
|
+
static createSeparator(columnWidths, character = "=") {
|
|
92
|
+
const totalWidth = columnWidths.reduce((sum, width) => sum + width + 3, 0) - 1;
|
|
93
|
+
return `+${padRight("", totalWidth, character)}+`;
|
|
94
|
+
}
|
|
95
|
+
static wrapText(text, maxLength) {
|
|
96
|
+
const words = text.split(" ");
|
|
97
|
+
const lines = [];
|
|
98
|
+
let currentLine = "";
|
|
99
|
+
for (const word of words) {
|
|
100
|
+
if (stringWidth(currentLine) + stringWidth(word) + 1 <= maxLength) {
|
|
101
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
102
|
+
} else {
|
|
103
|
+
if (currentLine) {
|
|
104
|
+
lines.push(currentLine);
|
|
105
|
+
}
|
|
106
|
+
if (stringWidth(word) <= maxLength) {
|
|
107
|
+
currentLine = word;
|
|
108
|
+
} else {
|
|
109
|
+
const subWords = _ASCIITableUtils.splitWord(word, maxLength);
|
|
110
|
+
lines.push(...subWords.slice(0, -1));
|
|
111
|
+
currentLine = subWords[subWords.length - 1];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (currentLine) {
|
|
116
|
+
lines.push(currentLine);
|
|
117
|
+
}
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
static splitWord(word, maxLength) {
|
|
121
|
+
const graphemes = splitGraphemes(word);
|
|
122
|
+
const subWords = [];
|
|
123
|
+
let currentSubWord = "";
|
|
124
|
+
for (const grapheme of graphemes) {
|
|
125
|
+
if (stringWidth(currentSubWord + grapheme) <= maxLength) {
|
|
126
|
+
currentSubWord += grapheme;
|
|
127
|
+
} else {
|
|
128
|
+
subWords.push(currentSubWord);
|
|
129
|
+
currentSubWord = grapheme;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (currentSubWord) {
|
|
133
|
+
subWords.push(currentSubWord);
|
|
134
|
+
}
|
|
135
|
+
return subWords;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/lib/ascii-tables/multi-column-ascii-table.ts
|
|
140
|
+
import stringWidth2 from "string-width";
|
|
141
|
+
var MultiColumnASCIITable = class {
|
|
142
|
+
headers;
|
|
143
|
+
rows;
|
|
144
|
+
tableWidth;
|
|
145
|
+
emptyMessage;
|
|
146
|
+
widthMode;
|
|
147
|
+
constructor(headers, options = {}) {
|
|
148
|
+
this.headers = headers;
|
|
149
|
+
this.rows = [];
|
|
150
|
+
this.tableWidth = options.tableWidth || 80;
|
|
151
|
+
this.emptyMessage = options.emptyMessage || "";
|
|
152
|
+
this.widthMode = options.widthMode || "flex";
|
|
153
|
+
const minTableWidth = this.getMinimumWidth();
|
|
154
|
+
if (this.tableWidth < minTableWidth) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`Table width must be at least ${minTableWidth} to accommodate the headers.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
getMinimumWidth() {
|
|
161
|
+
return this.headers.length * 4 + 1;
|
|
162
|
+
}
|
|
163
|
+
addRow(row) {
|
|
164
|
+
if (row.length !== this.headers.length) {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Number of values in the row (${row.length}) must match the number of headers (${this.headers.length}).`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
this.rows.push(row);
|
|
170
|
+
}
|
|
171
|
+
toString(options = {}) {
|
|
172
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
173
|
+
const emptyMessage = options.emptyMessage || this.emptyMessage;
|
|
174
|
+
if (this.rows.length === 0) {
|
|
175
|
+
const emptyTableWidth = Math.min(tableWidth, 40);
|
|
176
|
+
const separator = "+" + "-".repeat(emptyTableWidth - 2) + "+";
|
|
177
|
+
const emptyMessageLines = ASCIITableUtils.wrapText(
|
|
178
|
+
emptyMessage,
|
|
179
|
+
emptyTableWidth - 4
|
|
180
|
+
);
|
|
181
|
+
const emptyRows = emptyMessageLines.map((line) => {
|
|
182
|
+
const paddingLeft = " ".repeat(
|
|
183
|
+
Math.floor((emptyTableWidth - stringWidth2(line) - 4) / 2)
|
|
184
|
+
);
|
|
185
|
+
const paddingRight = " ".repeat(
|
|
186
|
+
Math.ceil((emptyTableWidth - stringWidth2(line) - 4) / 2)
|
|
187
|
+
);
|
|
188
|
+
return `| ${paddingLeft}${line}${paddingRight} |`;
|
|
189
|
+
});
|
|
190
|
+
if (emptyRows.length === 0) {
|
|
191
|
+
emptyRows.push(`| ${" ".repeat(emptyTableWidth - 4)} |`);
|
|
192
|
+
}
|
|
193
|
+
return [separator, ...emptyRows, separator].join("\n");
|
|
194
|
+
}
|
|
195
|
+
const columnWidths = this.calculateColumnWidths(options);
|
|
196
|
+
const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);
|
|
197
|
+
const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, "-");
|
|
198
|
+
let tableString = headerSeparator + "\n";
|
|
199
|
+
const header = this.renderRow(this.headers, columnWidths);
|
|
200
|
+
tableString += header + "\n" + rowSeparator + "\n";
|
|
201
|
+
const rows = this.rows.map((row) => {
|
|
202
|
+
return this.renderRow(row, columnWidths);
|
|
203
|
+
});
|
|
204
|
+
tableString += rows.join("\n" + rowSeparator + "\n");
|
|
205
|
+
tableString += "\n" + headerSeparator;
|
|
206
|
+
return tableString;
|
|
207
|
+
}
|
|
208
|
+
calculateColumnWidths(options = {}) {
|
|
209
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
210
|
+
const widthMode = options.widthMode || this.widthMode;
|
|
211
|
+
const numColumns = this.headers.length;
|
|
212
|
+
if (widthMode === "fixed") {
|
|
213
|
+
const availableWidth2 = tableWidth - (numColumns + 1) * 3 + 1;
|
|
214
|
+
const columnWidth = Math.floor(availableWidth2 / numColumns);
|
|
215
|
+
const extraWidth = availableWidth2 % numColumns;
|
|
216
|
+
const columnWidths = new Array(numColumns).fill(columnWidth);
|
|
217
|
+
for (let i = 0; i < extraWidth; i++) {
|
|
218
|
+
columnWidths[i] += 1;
|
|
219
|
+
}
|
|
220
|
+
return columnWidths;
|
|
221
|
+
}
|
|
222
|
+
const availableWidth = tableWidth - (numColumns + 1) * 3 + 1;
|
|
223
|
+
const maxColumnWidth = Math.floor(availableWidth / numColumns);
|
|
224
|
+
const totalContentWidth = this.headers.reduce(
|
|
225
|
+
(sum, header) => sum + stringWidth2(header),
|
|
226
|
+
0
|
|
227
|
+
);
|
|
228
|
+
if (availableWidth >= totalContentWidth) {
|
|
229
|
+
const remainingWidth = availableWidth - totalContentWidth;
|
|
230
|
+
const extraCharWidth = Math.floor(remainingWidth / numColumns);
|
|
231
|
+
const extraCharRemainder = remainingWidth % numColumns;
|
|
232
|
+
const columnWidths = this.headers.map((header, index) => {
|
|
233
|
+
const extraWidth = index < extraCharRemainder ? 1 : 0;
|
|
234
|
+
return stringWidth2(header) + extraCharWidth + extraWidth;
|
|
235
|
+
});
|
|
236
|
+
return columnWidths;
|
|
237
|
+
} else {
|
|
238
|
+
const columnWidths = this.headers.map(() => maxColumnWidth);
|
|
239
|
+
return columnWidths;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
renderRow(row, columnWidths) {
|
|
243
|
+
const wrappedCells = row.map((value, index) => {
|
|
244
|
+
const wrappedLines = ASCIITableUtils.wrapText(value, columnWidths[index]);
|
|
245
|
+
return wrappedLines.map((line) => line.padEnd(columnWidths[index])).join("\n");
|
|
246
|
+
});
|
|
247
|
+
const maxLines = Math.max(
|
|
248
|
+
...wrappedCells.map((cell) => cell.split("\n").length)
|
|
249
|
+
);
|
|
250
|
+
const paddedRows = [];
|
|
251
|
+
for (let i = 0; i < maxLines; i++) {
|
|
252
|
+
const rowLine = wrappedCells.map((cell, index) => {
|
|
253
|
+
const cellLines = cell.split("\n");
|
|
254
|
+
const cellLine = cellLines[i] || "";
|
|
255
|
+
const padding = " ".repeat(columnWidths[index] - stringWidth2(cellLine));
|
|
256
|
+
return " " + cellLine + padding + " ";
|
|
257
|
+
});
|
|
258
|
+
paddedRows.push("|" + rowLine.join("|") + "|");
|
|
259
|
+
}
|
|
260
|
+
return paddedRows.join("\n");
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/lib/ascii-tables/key-value-ascii-table.ts
|
|
265
|
+
import stringWidth3 from "string-width";
|
|
266
|
+
|
|
267
|
+
// src/lib/clamp.ts
|
|
268
|
+
function clamp(value, min, max) {
|
|
269
|
+
return Math.max(min, Math.min(value, max));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/lib/ascii-tables/key-value-ascii-table.ts
|
|
273
|
+
var KeyValueASCIITable = class _KeyValueASCIITable {
|
|
274
|
+
tableWidth;
|
|
275
|
+
emptyMessage;
|
|
276
|
+
autoAdjustWidthWhenPossible = true;
|
|
277
|
+
rows = [];
|
|
278
|
+
constructor(options = {}) {
|
|
279
|
+
const minTableWidth = this.getMinimumWidth();
|
|
280
|
+
if (options.tableWidth && options.tableWidth < minTableWidth) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Table width must be at least ${minTableWidth} to accommodate the table structure.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
this.tableWidth = options.tableWidth || 80;
|
|
286
|
+
this.autoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? true;
|
|
287
|
+
this.emptyMessage = options.emptyMessage || "";
|
|
288
|
+
}
|
|
289
|
+
getMinimumWidth() {
|
|
290
|
+
return 9;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Adds key and value to the table but placing the value on its own row.
|
|
294
|
+
*
|
|
295
|
+
* @param key
|
|
296
|
+
* @param value
|
|
297
|
+
*/
|
|
298
|
+
addValueOnSeparateRow(key, value) {
|
|
299
|
+
const row = { kind: "own", key, value };
|
|
300
|
+
this.rows.push(row);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Adds key and value to the table.
|
|
304
|
+
*
|
|
305
|
+
* If provided value is an instance of ASCIITable or MultiColumnASCIITable, it will be rendered as a nested table on its own row for readability.
|
|
306
|
+
*
|
|
307
|
+
* @param key
|
|
308
|
+
* @param value
|
|
309
|
+
*/
|
|
310
|
+
addRow(key, value) {
|
|
311
|
+
const row = { kind: "regular", key, value };
|
|
312
|
+
this.rows.push(row);
|
|
313
|
+
}
|
|
314
|
+
toString(options = {}) {
|
|
315
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
316
|
+
const canAutoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;
|
|
317
|
+
const emptyMessage = options.emptyMessage || this.emptyMessage;
|
|
318
|
+
if (this.rows.length === 0) {
|
|
319
|
+
const emptyTableWidth = Math.min(tableWidth, 40);
|
|
320
|
+
const separator = "+" + "-".repeat(emptyTableWidth - 2) + "+";
|
|
321
|
+
const emptyMessageLines = ASCIITableUtils.wrapText(
|
|
322
|
+
emptyMessage,
|
|
323
|
+
emptyTableWidth - 4
|
|
324
|
+
);
|
|
325
|
+
const emptyRows = emptyMessageLines.map((line) => {
|
|
326
|
+
const paddingLeft = padRight(
|
|
327
|
+
"",
|
|
328
|
+
Math.floor((emptyTableWidth - stringWidth3(line) - 4) / 2),
|
|
329
|
+
" "
|
|
330
|
+
);
|
|
331
|
+
const paddingRight = padRight(
|
|
332
|
+
"",
|
|
333
|
+
Math.ceil((emptyTableWidth - stringWidth3(line) - 4) / 2),
|
|
334
|
+
" "
|
|
335
|
+
);
|
|
336
|
+
return `| ${paddingLeft}${line}${paddingRight} |`;
|
|
337
|
+
});
|
|
338
|
+
if (emptyRows.length === 0) {
|
|
339
|
+
emptyRows.push(`| ${" ".repeat(emptyTableWidth - 4)} |`);
|
|
340
|
+
}
|
|
341
|
+
return [separator, ...emptyRows, separator].join("\n");
|
|
342
|
+
}
|
|
343
|
+
const columnWidths = this.calculateColumnWidths(options);
|
|
344
|
+
const headerSeparator = ASCIITableUtils.createSeparator(columnWidths);
|
|
345
|
+
const rowSeparator = ASCIITableUtils.createSeparator(columnWidths, "-");
|
|
346
|
+
let tableString = headerSeparator + "\n";
|
|
347
|
+
for (const [rowIndex, row] of this.rows.entries()) {
|
|
348
|
+
const { kind, key, value } = row;
|
|
349
|
+
if (kind === "own" || value instanceof _KeyValueASCIITable || value instanceof MultiColumnASCIITable || Array.isArray(value)) {
|
|
350
|
+
const keyString = ASCIITableUtils.centerText(
|
|
351
|
+
key,
|
|
352
|
+
columnWidths[0] + columnWidths[1] + 3
|
|
353
|
+
);
|
|
354
|
+
tableString += `| ${keyString} |
|
|
355
|
+
`;
|
|
356
|
+
tableString += rowSeparator + "\n";
|
|
357
|
+
let valueString = "";
|
|
358
|
+
if (value instanceof _KeyValueASCIITable) {
|
|
359
|
+
valueString = this.formatValue(
|
|
360
|
+
value,
|
|
361
|
+
tableWidth - 4,
|
|
362
|
+
canAutoAdjustWidthWhenPossible,
|
|
363
|
+
""
|
|
364
|
+
);
|
|
365
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
366
|
+
valueString = this.formatValue(
|
|
367
|
+
value,
|
|
368
|
+
tableWidth - 4,
|
|
369
|
+
canAutoAdjustWidthWhenPossible,
|
|
370
|
+
""
|
|
371
|
+
);
|
|
372
|
+
} else if (Array.isArray(value)) {
|
|
373
|
+
valueString = this.formatValue(
|
|
374
|
+
value,
|
|
375
|
+
tableWidth - 4,
|
|
376
|
+
canAutoAdjustWidthWhenPossible,
|
|
377
|
+
""
|
|
378
|
+
);
|
|
379
|
+
} else if (row.kind === "own") {
|
|
380
|
+
valueString = this.formatTableRowOnOwnRow(
|
|
381
|
+
value,
|
|
382
|
+
tableWidth - 4,
|
|
383
|
+
tableWidth
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const valueLines = valueString.split("\n");
|
|
387
|
+
const paddedValueLines = valueLines.map((line) => {
|
|
388
|
+
const padding = padRight("", tableWidth - stringWidth3(line) - 4, " ");
|
|
389
|
+
return `| ${line}${padding} |`;
|
|
390
|
+
});
|
|
391
|
+
tableString += paddedValueLines.join("\n") + "\n";
|
|
392
|
+
tableString += headerSeparator + "\n";
|
|
393
|
+
} else {
|
|
394
|
+
const keyLines = ASCIITableUtils.wrapText(key, columnWidths[0]);
|
|
395
|
+
const valueLines = ASCIITableUtils.wrapText(
|
|
396
|
+
this.formatValue(
|
|
397
|
+
value,
|
|
398
|
+
columnWidths[1],
|
|
399
|
+
canAutoAdjustWidthWhenPossible,
|
|
400
|
+
""
|
|
401
|
+
),
|
|
402
|
+
columnWidths[1]
|
|
403
|
+
);
|
|
404
|
+
const maxLines = Math.max(keyLines.length, valueLines.length);
|
|
405
|
+
for (let i = 0; i < maxLines; i++) {
|
|
406
|
+
const keyLine = keyLines[i] || "";
|
|
407
|
+
const valueLine = valueLines[i] || "";
|
|
408
|
+
const keyPadding = " ".repeat(columnWidths[0] - stringWidth3(keyLine));
|
|
409
|
+
const valuePadding = " ".repeat(
|
|
410
|
+
columnWidths[1] - stringWidth3(valueLine)
|
|
411
|
+
);
|
|
412
|
+
tableString += `| ${keyLine}${keyPadding} | ${valueLine}${valuePadding} |
|
|
413
|
+
`;
|
|
414
|
+
if (i === maxLines - 1) {
|
|
415
|
+
if (rowIndex === this.rows.length - 1) {
|
|
416
|
+
tableString += headerSeparator + "\n";
|
|
417
|
+
} else {
|
|
418
|
+
tableString += rowSeparator + "\n";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return tableString.trim();
|
|
425
|
+
}
|
|
426
|
+
calculateColumnWidths(options = {}) {
|
|
427
|
+
const tableWidth = options.tableWidth || this.tableWidth;
|
|
428
|
+
const canAutoAdjustWidthWhenPossible = options.autoAdjustWidthWhenPossible ?? this.autoAdjustWidthWhenPossible;
|
|
429
|
+
const columnWidths = [0, 0];
|
|
430
|
+
for (const row of this.rows) {
|
|
431
|
+
const { key, value } = row;
|
|
432
|
+
const keyWidth = stringWidth3(key);
|
|
433
|
+
const maxKeyWidth = Math.floor((tableWidth - 7) / 2);
|
|
434
|
+
if (keyWidth > columnWidths[0]) {
|
|
435
|
+
columnWidths[0] = Math.min(keyWidth, maxKeyWidth);
|
|
436
|
+
columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);
|
|
437
|
+
}
|
|
438
|
+
if (typeof value === "string") {
|
|
439
|
+
const valueWidth = Math.max(
|
|
440
|
+
...value.split("\n").map((line) => stringWidth3(line))
|
|
441
|
+
);
|
|
442
|
+
if (valueWidth > columnWidths[1]) {
|
|
443
|
+
columnWidths[1] = Math.min(
|
|
444
|
+
valueWidth,
|
|
445
|
+
tableWidth - columnWidths[0] - 7
|
|
446
|
+
);
|
|
447
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
448
|
+
}
|
|
449
|
+
} else if (row.kind === "own") {
|
|
450
|
+
let valueWidth = 0;
|
|
451
|
+
if (isString(value)) {
|
|
452
|
+
valueWidth = Math.max(
|
|
453
|
+
...value.split("\n").map((line) => stringWidth3(line))
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (valueWidth > columnWidths[1]) {
|
|
457
|
+
columnWidths[1] = Math.min(
|
|
458
|
+
valueWidth,
|
|
459
|
+
tableWidth - columnWidths[0] - 7
|
|
460
|
+
);
|
|
461
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
462
|
+
}
|
|
463
|
+
} else if (value instanceof _KeyValueASCIITable) {
|
|
464
|
+
let nestedTableColumnWidths = [];
|
|
465
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
466
|
+
const minWidth = value.getMinimumWidth();
|
|
467
|
+
const availableWidth2 = tableWidth - columnWidths[0] - 7;
|
|
468
|
+
const adjustedWidth = clamp(availableWidth2, minWidth, availableWidth2);
|
|
469
|
+
nestedTableColumnWidths = value.calculateColumnWidths({
|
|
470
|
+
tableWidth: adjustedWidth
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
nestedTableColumnWidths = value.calculateColumnWidths();
|
|
474
|
+
}
|
|
475
|
+
const nestedTableWidth = nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) + nestedTableColumnWidths.length * 3 - 1;
|
|
476
|
+
const availableWidth = tableWidth - columnWidths[0] - 7;
|
|
477
|
+
if (nestedTableWidth > availableWidth) {
|
|
478
|
+
columnWidths[1] = availableWidth;
|
|
479
|
+
} else {
|
|
480
|
+
columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);
|
|
481
|
+
}
|
|
482
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
483
|
+
let nestedTableColumnWidths = [];
|
|
484
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
485
|
+
const minWidth = value.getMinimumWidth();
|
|
486
|
+
const availableWidth2 = tableWidth - columnWidths[0] - 7;
|
|
487
|
+
const adjustedWidth = clamp(availableWidth2, minWidth, availableWidth2);
|
|
488
|
+
nestedTableColumnWidths = value.calculateColumnWidths({
|
|
489
|
+
tableWidth: adjustedWidth
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
nestedTableColumnWidths = value.calculateColumnWidths();
|
|
493
|
+
}
|
|
494
|
+
const nestedTableWidth = nestedTableColumnWidths.reduce((sum, width) => sum + width, 0) + nestedTableColumnWidths.length * 3 - 1;
|
|
495
|
+
const availableWidth = tableWidth - columnWidths[0] - 7;
|
|
496
|
+
if (nestedTableWidth > availableWidth) {
|
|
497
|
+
columnWidths[1] = availableWidth;
|
|
498
|
+
} else {
|
|
499
|
+
columnWidths[1] = Math.max(columnWidths[1], nestedTableWidth);
|
|
500
|
+
}
|
|
501
|
+
} else if (Array.isArray(value)) {
|
|
502
|
+
for (const nestedCell of value) {
|
|
503
|
+
const nestedKeyWidth = stringWidth3(nestedCell.key);
|
|
504
|
+
const maxNestedKeyWidth = Math.floor((tableWidth - 7) / 2);
|
|
505
|
+
if (nestedKeyWidth > columnWidths[0]) {
|
|
506
|
+
columnWidths[0] = Math.min(nestedKeyWidth, maxNestedKeyWidth);
|
|
507
|
+
columnWidths[1] = Math.max(0, tableWidth - columnWidths[0] - 7);
|
|
508
|
+
}
|
|
509
|
+
if (typeof nestedCell.value === "string") {
|
|
510
|
+
const nestedValueWidth = Math.max(
|
|
511
|
+
...nestedCell.value.split("\n").map((line) => stringWidth3(line))
|
|
512
|
+
);
|
|
513
|
+
if (nestedValueWidth > columnWidths[1]) {
|
|
514
|
+
columnWidths[1] = Math.min(
|
|
515
|
+
nestedValueWidth,
|
|
516
|
+
tableWidth - columnWidths[0] - 7
|
|
517
|
+
);
|
|
518
|
+
columnWidths[0] = Math.max(0, tableWidth - columnWidths[1] - 7);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return columnWidths;
|
|
525
|
+
}
|
|
526
|
+
formatValue(value, cellWidth, canAutoAdjustWidthWhenPossible, indent = "") {
|
|
527
|
+
if (typeof value === "string") {
|
|
528
|
+
return value;
|
|
529
|
+
} else if (typeof value === "number") {
|
|
530
|
+
return String(value);
|
|
531
|
+
} else if (typeof value === "boolean") {
|
|
532
|
+
return String(value);
|
|
533
|
+
} else if (value === null) {
|
|
534
|
+
return "null";
|
|
535
|
+
} else if (value === void 0) {
|
|
536
|
+
return "undefined";
|
|
537
|
+
} else if (value instanceof _KeyValueASCIITable) {
|
|
538
|
+
let nestedTableLines;
|
|
539
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
540
|
+
const minWidth = value.getMinimumWidth();
|
|
541
|
+
const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);
|
|
542
|
+
nestedTableLines = value.toString({ tableWidth: adjustedWidth }).split("\n");
|
|
543
|
+
} else {
|
|
544
|
+
nestedTableLines = value.toString().split("\n");
|
|
545
|
+
}
|
|
546
|
+
const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);
|
|
547
|
+
return indentedLines.join("\n");
|
|
548
|
+
} else if (value instanceof MultiColumnASCIITable) {
|
|
549
|
+
let nestedTableLines;
|
|
550
|
+
if (canAutoAdjustWidthWhenPossible) {
|
|
551
|
+
const minWidth = value.getMinimumWidth();
|
|
552
|
+
const adjustedWidth = clamp(cellWidth, minWidth, cellWidth);
|
|
553
|
+
nestedTableLines = value.toString({ tableWidth: adjustedWidth }).split("\n");
|
|
554
|
+
} else {
|
|
555
|
+
nestedTableLines = value.toString().split("\n");
|
|
556
|
+
}
|
|
557
|
+
const indentedLines = nestedTableLines.map((line) => `${indent}${line}`);
|
|
558
|
+
return indentedLines.join("\n");
|
|
559
|
+
} else if (Array.isArray(value)) {
|
|
560
|
+
const nestedValueLines = [];
|
|
561
|
+
for (const { key, value: nestedValue } of value) {
|
|
562
|
+
const formattedKey = `${indent}${key}:`;
|
|
563
|
+
const formattedValue = this.formatValue(
|
|
564
|
+
nestedValue,
|
|
565
|
+
cellWidth - indent.length - stringWidth3(key) - 2,
|
|
566
|
+
canAutoAdjustWidthWhenPossible,
|
|
567
|
+
`${indent}`
|
|
568
|
+
);
|
|
569
|
+
const wrappedSpacer = padRight("", 4, " ");
|
|
570
|
+
const wrappedValue = formattedValue.split("\n").map((line) => `${indent}${wrappedSpacer}${line}`);
|
|
571
|
+
nestedValueLines.push(formattedKey);
|
|
572
|
+
nestedValueLines.push(...wrappedValue);
|
|
573
|
+
nestedValueLines.push("");
|
|
574
|
+
}
|
|
575
|
+
return nestedValueLines.slice(0, -1).join("\n");
|
|
576
|
+
} else {
|
|
577
|
+
throw new TypeError("Invalid value type provided");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
formatTableRowOnOwnRow(value, width, maxRowLength) {
|
|
581
|
+
const lines = value.split("\n");
|
|
582
|
+
const paddedLines = lines.map((line) => {
|
|
583
|
+
const wrappedLines = ASCIITableUtils.wrapText(line, maxRowLength - 4);
|
|
584
|
+
return wrappedLines.map((wrappedLine) => {
|
|
585
|
+
const padding = padRight(
|
|
586
|
+
"",
|
|
587
|
+
width - stringWidth3(wrappedLine) - 2,
|
|
588
|
+
" "
|
|
589
|
+
);
|
|
590
|
+
return `${wrappedLine}${padding}`;
|
|
591
|
+
}).join("\n");
|
|
592
|
+
});
|
|
593
|
+
return paddedLines.join("\n");
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// src/lib/error-to-string.ts
|
|
598
|
+
function safeStringify(value) {
|
|
599
|
+
if (value === null || value === void 0) {
|
|
600
|
+
return String(value);
|
|
601
|
+
}
|
|
602
|
+
switch (typeof value) {
|
|
603
|
+
case "string":
|
|
604
|
+
return value;
|
|
605
|
+
case "number":
|
|
606
|
+
case "boolean":
|
|
607
|
+
case "bigint":
|
|
608
|
+
return String(value);
|
|
609
|
+
case "object":
|
|
610
|
+
return JSON.stringify(value);
|
|
611
|
+
case "function":
|
|
612
|
+
return "[Function]";
|
|
613
|
+
case "symbol":
|
|
614
|
+
return value.toString();
|
|
615
|
+
default:
|
|
616
|
+
return String(value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function errorToString(error, maxRowLength = 80) {
|
|
620
|
+
const table = errorToASCIITable(error, maxRowLength);
|
|
621
|
+
return table.toString();
|
|
622
|
+
}
|
|
623
|
+
function errorToASCIITable(error, maxRowLength) {
|
|
624
|
+
const table = new KeyValueASCIITable({
|
|
625
|
+
tableWidth: maxRowLength,
|
|
626
|
+
autoAdjustWidthWhenPossible: true
|
|
627
|
+
});
|
|
628
|
+
if (error && typeof error === "object") {
|
|
629
|
+
const err = error;
|
|
630
|
+
table.addRow("Key", "Value");
|
|
631
|
+
if (err["message"]) {
|
|
632
|
+
table.addRow("Message", safeStringify(err["message"]));
|
|
633
|
+
}
|
|
634
|
+
if (err["name"]) {
|
|
635
|
+
table.addRow("Name", safeStringify(err["name"]));
|
|
636
|
+
}
|
|
637
|
+
if (err["code"]) {
|
|
638
|
+
table.addRow("Code", safeStringify(err["code"]));
|
|
639
|
+
}
|
|
640
|
+
if (err["errno"]) {
|
|
641
|
+
table.addRow("Errno", safeStringify(err["errno"]));
|
|
642
|
+
}
|
|
643
|
+
if (err["errPrefix"]) {
|
|
644
|
+
table.addRow("Prefix", safeStringify(err["errPrefix"]));
|
|
645
|
+
}
|
|
646
|
+
if (err["errType"]) {
|
|
647
|
+
table.addRow("errType", safeStringify(err["errType"]));
|
|
648
|
+
}
|
|
649
|
+
if (err["errCode"]) {
|
|
650
|
+
table.addRow("errCode", safeStringify(err["errCode"]));
|
|
651
|
+
}
|
|
652
|
+
if (err["additionalInfo"]) {
|
|
653
|
+
const additionalInfo = err["additionalInfo"];
|
|
654
|
+
const sensitiveFieldNames = err["sensitiveFieldNames"] || [];
|
|
655
|
+
for (const key in additionalInfo) {
|
|
656
|
+
if (sensitiveFieldNames.includes(key)) {
|
|
657
|
+
table.addRow(`AdditionalInfo.${key}`, "***");
|
|
658
|
+
} else {
|
|
659
|
+
const value = additionalInfo[key];
|
|
660
|
+
table.addRow(
|
|
661
|
+
`AdditionalInfo.${key}`,
|
|
662
|
+
stringifyValue(value, table, maxRowLength)
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (err["stack"]) {
|
|
668
|
+
table.addValueOnSeparateRow("Stack", safeStringify(err["stack"]));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return table;
|
|
672
|
+
}
|
|
673
|
+
function stringifyValue(value, table, maxRowLength) {
|
|
674
|
+
if (typeof value === "string") {
|
|
675
|
+
return value;
|
|
676
|
+
} else if (Array.isArray(value)) {
|
|
677
|
+
return value.map((item) => {
|
|
678
|
+
const result = stringifyValue(item, table, maxRowLength);
|
|
679
|
+
if (typeof result === "string") {
|
|
680
|
+
return result;
|
|
681
|
+
} else if (result instanceof KeyValueASCIITable) {
|
|
682
|
+
return result.toString();
|
|
683
|
+
} else {
|
|
684
|
+
return JSON.stringify(result);
|
|
685
|
+
}
|
|
686
|
+
}).join(", ");
|
|
687
|
+
} else if (typeof value === "object" && value !== null) {
|
|
688
|
+
if (value instanceof Error) {
|
|
689
|
+
return errorToASCIITable(value, maxRowLength - 4);
|
|
690
|
+
} else {
|
|
691
|
+
const entries = Object.entries(value).map(
|
|
692
|
+
([key, val]) => ({
|
|
693
|
+
key,
|
|
694
|
+
value: stringifyValue(val, table, maxRowLength - 4)
|
|
695
|
+
})
|
|
696
|
+
);
|
|
697
|
+
return entries;
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
return String(value);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/lib/is-promise.ts
|
|
705
|
+
function isPromise(obj) {
|
|
706
|
+
return !!obj && (typeof obj === "object" || typeof obj === "function") && // @ts-expect-error - obj is checked to be object/function, then property access works at runtime
|
|
707
|
+
typeof obj["then"] === "function";
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/lib/is-function.ts
|
|
711
|
+
function isFunction(value) {
|
|
712
|
+
return typeof value === "function" || value instanceof Function;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/lib/safe-handle-callback.ts
|
|
716
|
+
function safeHandleCallback(callbackName, callback, ...args) {
|
|
717
|
+
const handleError = (error) => {
|
|
718
|
+
if (typeof globalThis.dispatchEvent === "function") {
|
|
719
|
+
globalThis.dispatchEvent(
|
|
720
|
+
new ErrorEvent("reportError", {
|
|
721
|
+
error: new Error(
|
|
722
|
+
`Error in a callback ${callbackName}: ${DOUBLE_EOL}${errorToString(error)}`
|
|
723
|
+
)
|
|
724
|
+
})
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
if (isFunction(callback)) {
|
|
729
|
+
try {
|
|
730
|
+
const result = callback(...args);
|
|
731
|
+
if (isPromise(result)) {
|
|
732
|
+
result.catch((error) => {
|
|
733
|
+
handleError(error);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
} catch (error) {
|
|
737
|
+
handleError(error);
|
|
738
|
+
}
|
|
739
|
+
} else {
|
|
740
|
+
handleError(
|
|
741
|
+
new Error(`Callback provided for ${callbackName} is not a function`)
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/lib/process-signal-manager.ts
|
|
747
|
+
import { ulid } from "ulid";
|
|
748
|
+
import readline from "readline";
|
|
749
|
+
var SHARED_STATE_KEY = /* @__PURE__ */ Symbol.for("lifecycleion.ProcessSignalManager.v1");
|
|
750
|
+
function getSharedState() {
|
|
751
|
+
const g = globalThis;
|
|
752
|
+
if (!g[SHARED_STATE_KEY]) {
|
|
753
|
+
g[SHARED_STATE_KEY] = {
|
|
754
|
+
keypressEventsEmittedOnStdin: false,
|
|
755
|
+
attachedInstances: /* @__PURE__ */ new Set(),
|
|
756
|
+
rawModeOwner: null,
|
|
757
|
+
rawModeEnabledByManager: false
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return g[SHARED_STATE_KEY];
|
|
761
|
+
}
|
|
762
|
+
function transferRawModeOwnership(shared, currentOwner) {
|
|
763
|
+
if (shared.rawModeOwner !== currentOwner) {
|
|
764
|
+
return shared.rawModeOwner;
|
|
765
|
+
}
|
|
766
|
+
if (shared.attachedInstances.size === 0) {
|
|
767
|
+
shared.rawModeOwner = null;
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
for (const candidateID of shared.attachedInstances) {
|
|
771
|
+
if (shared.attachedInstances.has(candidateID)) {
|
|
772
|
+
shared.rawModeOwner = candidateID;
|
|
773
|
+
return candidateID;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
shared.rawModeOwner = null;
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
var ProcessSignalManager = class {
|
|
780
|
+
// Unique identifier for this instance, used for tracking in shared state
|
|
781
|
+
instanceID;
|
|
782
|
+
onShutdownRequested;
|
|
783
|
+
onReloadRequested;
|
|
784
|
+
onInfoRequested;
|
|
785
|
+
onDebugRequested;
|
|
786
|
+
shutdownCallbackName;
|
|
787
|
+
reloadCallbackName;
|
|
788
|
+
infoCallbackName;
|
|
789
|
+
debugCallbackName;
|
|
790
|
+
shutdownSignalListeners;
|
|
791
|
+
reloadSignalListener;
|
|
792
|
+
infoSignalListener;
|
|
793
|
+
debugSignalListener;
|
|
794
|
+
keypressHandler;
|
|
795
|
+
_isAttached = false;
|
|
796
|
+
// Throttle state for keyboard events (default 200ms, 0 disables)
|
|
797
|
+
// Track throttle separately per action type so different keys don't interfere with each other
|
|
798
|
+
// Use -Infinity to ensure first press is never throttled (always fires immediately)
|
|
799
|
+
keypressThrottleMS;
|
|
800
|
+
lastActionTimes = {
|
|
801
|
+
shutdown: -Infinity,
|
|
802
|
+
reload: -Infinity,
|
|
803
|
+
info: -Infinity,
|
|
804
|
+
debug: -Infinity
|
|
805
|
+
};
|
|
806
|
+
constructor(options) {
|
|
807
|
+
this.instanceID = ulid();
|
|
808
|
+
this.onShutdownRequested = options.onShutdownRequested;
|
|
809
|
+
this.onReloadRequested = options.onReloadRequested;
|
|
810
|
+
this.onInfoRequested = options.onInfoRequested;
|
|
811
|
+
this.onDebugRequested = options.onDebugRequested;
|
|
812
|
+
this.shutdownCallbackName = options.shutdownCallbackName ?? "onShutdownRequested";
|
|
813
|
+
this.reloadCallbackName = options.reloadCallbackName ?? "onReloadRequested";
|
|
814
|
+
this.infoCallbackName = options.infoCallbackName ?? "onInfoRequested";
|
|
815
|
+
this.debugCallbackName = options.debugCallbackName ?? "onDebugRequested";
|
|
816
|
+
this.keypressThrottleMS = options.keypressThrottleMS ?? 200;
|
|
817
|
+
if (this.onShutdownRequested) {
|
|
818
|
+
const shutdownCallback = this.onShutdownRequested;
|
|
819
|
+
this.shutdownSignalListeners = {
|
|
820
|
+
SIGINT: () => safeHandleCallback(
|
|
821
|
+
this.shutdownCallbackName,
|
|
822
|
+
shutdownCallback,
|
|
823
|
+
"SIGINT"
|
|
824
|
+
),
|
|
825
|
+
SIGTERM: () => safeHandleCallback(
|
|
826
|
+
this.shutdownCallbackName,
|
|
827
|
+
shutdownCallback,
|
|
828
|
+
"SIGTERM"
|
|
829
|
+
),
|
|
830
|
+
SIGTRAP: () => safeHandleCallback(
|
|
831
|
+
this.shutdownCallbackName,
|
|
832
|
+
shutdownCallback,
|
|
833
|
+
"SIGTRAP"
|
|
834
|
+
)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (this.onReloadRequested) {
|
|
838
|
+
const reloadCallback = this.onReloadRequested;
|
|
839
|
+
this.reloadSignalListener = () => safeHandleCallback(this.reloadCallbackName, reloadCallback);
|
|
840
|
+
}
|
|
841
|
+
if (this.onInfoRequested) {
|
|
842
|
+
const infoCallback = this.onInfoRequested;
|
|
843
|
+
this.infoSignalListener = () => safeHandleCallback(this.infoCallbackName, infoCallback);
|
|
844
|
+
}
|
|
845
|
+
if (this.onDebugRequested) {
|
|
846
|
+
const debugCallback = this.onDebugRequested;
|
|
847
|
+
this.debugSignalListener = () => safeHandleCallback(this.debugCallbackName, debugCallback);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Check if the manager is currently attached to signals and keypresses.
|
|
852
|
+
*/
|
|
853
|
+
get isAttached() {
|
|
854
|
+
return this._isAttached;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Get detailed status information about what the manager is attached to.
|
|
858
|
+
*
|
|
859
|
+
* @returns Status object with handler registration and attachment state
|
|
860
|
+
*/
|
|
861
|
+
getStatus() {
|
|
862
|
+
return {
|
|
863
|
+
isAttached: this._isAttached,
|
|
864
|
+
handlers: {
|
|
865
|
+
shutdown: !!this.onShutdownRequested,
|
|
866
|
+
reload: !!this.onReloadRequested,
|
|
867
|
+
info: !!this.onInfoRequested,
|
|
868
|
+
debug: !!this.onDebugRequested
|
|
869
|
+
},
|
|
870
|
+
listeningFor: {
|
|
871
|
+
shutdownSignals: this._isAttached && !!this.shutdownSignalListeners,
|
|
872
|
+
reloadSignal: this._isAttached && !!this.reloadSignalListener,
|
|
873
|
+
infoSignal: this._isAttached && !!this.infoSignalListener,
|
|
874
|
+
debugSignal: this._isAttached && !!this.debugSignalListener,
|
|
875
|
+
// Keypresses are only available if stdin is a TTY
|
|
876
|
+
keypresses: this._isAttached && process.stdin.isTTY && !!this.keypressHandler
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Attach signal handlers and start listening for process signals and keyboard events.
|
|
882
|
+
* Idempotent - calling multiple times has no effect.
|
|
883
|
+
*/
|
|
884
|
+
attach() {
|
|
885
|
+
if (this._isAttached) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
this.listenForShutdownSignals();
|
|
890
|
+
this.listenForReloadSignal();
|
|
891
|
+
this.listenForInfoSignal();
|
|
892
|
+
this.listenForDebugSignal();
|
|
893
|
+
this.listenForKeyPresses();
|
|
894
|
+
this._isAttached = true;
|
|
895
|
+
} catch (error) {
|
|
896
|
+
this.stopListeningForShutdownSignals();
|
|
897
|
+
this.stopListeningForReloadSignal();
|
|
898
|
+
this.stopListeningForInfoSignal();
|
|
899
|
+
this.stopListeningForDebugSignal();
|
|
900
|
+
this.restoreStdin();
|
|
901
|
+
throw error;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Detach signal handlers and stop listening for process signals and keyboard events.
|
|
906
|
+
* Cleans up all event listeners and restores stdin to normal mode.
|
|
907
|
+
*
|
|
908
|
+
* Idempotent - calling multiple times has no effect.
|
|
909
|
+
*/
|
|
910
|
+
detach() {
|
|
911
|
+
if (!this._isAttached) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
this.stopListeningForShutdownSignals();
|
|
916
|
+
this.stopListeningForReloadSignal();
|
|
917
|
+
this.stopListeningForInfoSignal();
|
|
918
|
+
this.stopListeningForDebugSignal();
|
|
919
|
+
this.restoreStdin();
|
|
920
|
+
} finally {
|
|
921
|
+
this._isAttached = false;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Manually trigger a shutdown event
|
|
926
|
+
* if the manager is attached and a shutdown handler is registered
|
|
927
|
+
*
|
|
928
|
+
* @param method - The shutdown method (SIGINT, SIGTERM, or SIGTRAP) that will be passed to the shutdown callback
|
|
929
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
930
|
+
*/
|
|
931
|
+
triggerShutdown(method, shouldBypassAttachCheck = false) {
|
|
932
|
+
if ((this._isAttached || shouldBypassAttachCheck) && this.onShutdownRequested) {
|
|
933
|
+
safeHandleCallback(
|
|
934
|
+
this.shutdownCallbackName,
|
|
935
|
+
this.onShutdownRequested,
|
|
936
|
+
method
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Manually trigger a reload event
|
|
942
|
+
* if the manager is attached and a reload handler is registered
|
|
943
|
+
*
|
|
944
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
945
|
+
*/
|
|
946
|
+
triggerReload(shouldBypassAttachCheck = false) {
|
|
947
|
+
if ((this._isAttached || shouldBypassAttachCheck) && this.onReloadRequested) {
|
|
948
|
+
safeHandleCallback(this.reloadCallbackName, this.onReloadRequested);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Manually trigger an info event
|
|
953
|
+
* if the manager is attached and an info handler is registered
|
|
954
|
+
*
|
|
955
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
956
|
+
*/
|
|
957
|
+
triggerInfo(shouldBypassAttachCheck = false) {
|
|
958
|
+
if ((this._isAttached || shouldBypassAttachCheck) && this.onInfoRequested) {
|
|
959
|
+
safeHandleCallback(this.infoCallbackName, this.onInfoRequested);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Manually trigger a debug event
|
|
964
|
+
* if the manager is attached and a debug handler is registered
|
|
965
|
+
*
|
|
966
|
+
* @param shouldBypassAttachCheck - If true, triggers the callback even when not attached (useful for testing)
|
|
967
|
+
*/
|
|
968
|
+
triggerDebug(shouldBypassAttachCheck = false) {
|
|
969
|
+
if ((this._isAttached || shouldBypassAttachCheck) && this.onDebugRequested) {
|
|
970
|
+
safeHandleCallback(this.debugCallbackName, this.onDebugRequested);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Check if an action should be throttled based on the last time it was successfully triggered.
|
|
975
|
+
* Uses leading-edge throttle: first press fires immediately, subsequent presses within the
|
|
976
|
+
* throttle window are ignored. Only updates timestamp when action is allowed (not throttled).
|
|
977
|
+
*
|
|
978
|
+
* This is the standard pattern for keyboard shortcuts and prevents accidental double-triggers
|
|
979
|
+
* while allowing predictable repeated actions at a maximum rate.
|
|
980
|
+
*
|
|
981
|
+
* @param action - The action type to check throttling for
|
|
982
|
+
* @returns true if the action should be throttled (ignored), false otherwise
|
|
983
|
+
*/
|
|
984
|
+
shouldThrottle(action) {
|
|
985
|
+
if (this.keypressThrottleMS <= 0) {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
const now = Date.now();
|
|
989
|
+
const timeSinceLastTrigger = now - this.lastActionTimes[action];
|
|
990
|
+
if (timeSinceLastTrigger < this.keypressThrottleMS) {
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
this.lastActionTimes[action] = now;
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Register handlers for all shutdown signals (SIGINT, SIGTERM, SIGTRAP) if callback is provided.
|
|
998
|
+
* Each signal will trigger the shutdown callback with the appropriate method.
|
|
999
|
+
*/
|
|
1000
|
+
listenForShutdownSignals() {
|
|
1001
|
+
if (this.shutdownSignalListeners) {
|
|
1002
|
+
for (const signal of Object.keys(
|
|
1003
|
+
this.shutdownSignalListeners
|
|
1004
|
+
)) {
|
|
1005
|
+
process.on(signal, this.shutdownSignalListeners[signal]);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Remove handlers for all shutdown signals if they were registered.
|
|
1011
|
+
* Uses the same function references to ensure proper cleanup.
|
|
1012
|
+
*/
|
|
1013
|
+
stopListeningForShutdownSignals() {
|
|
1014
|
+
if (this.shutdownSignalListeners) {
|
|
1015
|
+
for (const signal of Object.keys(
|
|
1016
|
+
this.shutdownSignalListeners
|
|
1017
|
+
)) {
|
|
1018
|
+
process.off(signal, this.shutdownSignalListeners[signal]);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Register handler for SIGHUP signal if reload callback is provided.
|
|
1024
|
+
* SIGHUP is commonly used to trigger configuration reloads.
|
|
1025
|
+
*/
|
|
1026
|
+
listenForReloadSignal() {
|
|
1027
|
+
if (this.reloadSignalListener) {
|
|
1028
|
+
process.on("SIGHUP", this.reloadSignalListener);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Remove handler for SIGHUP signal.
|
|
1033
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
1034
|
+
*/
|
|
1035
|
+
stopListeningForReloadSignal() {
|
|
1036
|
+
if (this.reloadSignalListener) {
|
|
1037
|
+
process.off("SIGHUP", this.reloadSignalListener);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Register handler for SIGUSR1 signal if info callback is provided.
|
|
1042
|
+
* SIGUSR1 is commonly used for printing stats, health checks, etc.
|
|
1043
|
+
*/
|
|
1044
|
+
listenForInfoSignal() {
|
|
1045
|
+
if (this.infoSignalListener) {
|
|
1046
|
+
process.on("SIGUSR1", this.infoSignalListener);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Remove handler for SIGUSR1 signal.
|
|
1051
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
1052
|
+
*/
|
|
1053
|
+
stopListeningForInfoSignal() {
|
|
1054
|
+
if (this.infoSignalListener) {
|
|
1055
|
+
process.off("SIGUSR1", this.infoSignalListener);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Register handler for SIGUSR2 signal if debug callback is provided.
|
|
1060
|
+
* SIGUSR2 is commonly used for toggling debug mode, dumping state, etc.
|
|
1061
|
+
*/
|
|
1062
|
+
listenForDebugSignal() {
|
|
1063
|
+
if (this.debugSignalListener) {
|
|
1064
|
+
process.on("SIGUSR2", this.debugSignalListener);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Remove handler for SIGUSR2 signal.
|
|
1069
|
+
* Uses the same function reference to ensure proper cleanup.
|
|
1070
|
+
*/
|
|
1071
|
+
stopListeningForDebugSignal() {
|
|
1072
|
+
if (this.debugSignalListener) {
|
|
1073
|
+
process.off("SIGUSR2", this.debugSignalListener);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Enable keyboard event listening if stdin is a TTY.
|
|
1078
|
+
* Sets stdin to raw mode and listens for Ctrl+C, Escape, R, I, and D keypresses.
|
|
1079
|
+
*
|
|
1080
|
+
* Note: Letter keys are case-insensitive (R/r, I/i, D/d all work).
|
|
1081
|
+
*
|
|
1082
|
+
* Uses add-then-check pattern to prevent race conditions:
|
|
1083
|
+
* 1. Add ourselves to attachedInstances first
|
|
1084
|
+
* 2. Check if we're the first (size === 1) to enable raw mode
|
|
1085
|
+
* This ensures no gap where another instance could read stale state.
|
|
1086
|
+
*/
|
|
1087
|
+
listenForKeyPresses() {
|
|
1088
|
+
if (!process.stdin.isTTY || this.keypressHandler) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const shared = getSharedState();
|
|
1092
|
+
if (!shared.keypressEventsEmittedOnStdin) {
|
|
1093
|
+
shared.keypressEventsEmittedOnStdin = true;
|
|
1094
|
+
readline.emitKeypressEvents(process.stdin);
|
|
1095
|
+
}
|
|
1096
|
+
this.keypressHandler = (str, key) => {
|
|
1097
|
+
const keyObj = key;
|
|
1098
|
+
const keyName = keyObj.name;
|
|
1099
|
+
if (keyObj.ctrl && keyName === "c" && this.onShutdownRequested) {
|
|
1100
|
+
if (this.shouldThrottle("shutdown")) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
safeHandleCallback(
|
|
1104
|
+
this.shutdownCallbackName,
|
|
1105
|
+
this.onShutdownRequested,
|
|
1106
|
+
"SIGINT"
|
|
1107
|
+
);
|
|
1108
|
+
} else if (keyName === "escape" && this.onShutdownRequested) {
|
|
1109
|
+
if (this.shouldThrottle("shutdown")) {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
safeHandleCallback(
|
|
1113
|
+
this.shutdownCallbackName,
|
|
1114
|
+
this.onShutdownRequested,
|
|
1115
|
+
"SIGINT"
|
|
1116
|
+
);
|
|
1117
|
+
} else if (keyName === "r" && this.onReloadRequested) {
|
|
1118
|
+
if (this.shouldThrottle("reload")) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
safeHandleCallback(this.reloadCallbackName, this.onReloadRequested);
|
|
1122
|
+
} else if (keyName === "i" && this.onInfoRequested) {
|
|
1123
|
+
if (this.shouldThrottle("info")) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
safeHandleCallback(this.infoCallbackName, this.onInfoRequested);
|
|
1127
|
+
} else if (keyName === "d" && this.onDebugRequested) {
|
|
1128
|
+
if (this.shouldThrottle("debug")) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
safeHandleCallback(this.debugCallbackName, this.onDebugRequested);
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
shared.attachedInstances.add(this.instanceID);
|
|
1135
|
+
const isFirstInstance = shared.attachedInstances.size === 1;
|
|
1136
|
+
try {
|
|
1137
|
+
process.stdin.on("keypress", this.keypressHandler);
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
shared.attachedInstances.delete(this.instanceID);
|
|
1140
|
+
this.keypressHandler = void 0;
|
|
1141
|
+
throw error;
|
|
1142
|
+
}
|
|
1143
|
+
if (isFirstInstance) {
|
|
1144
|
+
if (!process.stdin.isRaw) {
|
|
1145
|
+
try {
|
|
1146
|
+
process.stdin.setRawMode(true);
|
|
1147
|
+
shared.rawModeOwner = this.instanceID;
|
|
1148
|
+
shared.rawModeEnabledByManager = true;
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
this.cleanupKeypressHandler(shared, true);
|
|
1151
|
+
throw error;
|
|
1152
|
+
}
|
|
1153
|
+
} else if (shared.rawModeEnabledByManager && (shared.rawModeOwner === null || !shared.attachedInstances.has(shared.rawModeOwner))) {
|
|
1154
|
+
shared.rawModeOwner = this.instanceID;
|
|
1155
|
+
} else if (!shared.rawModeEnabledByManager) {
|
|
1156
|
+
shared.rawModeOwner = null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (isFirstInstance) {
|
|
1160
|
+
try {
|
|
1161
|
+
process.stdin.resume();
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
this.cleanupKeypressHandler(shared);
|
|
1164
|
+
throw error;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Helper to clean up keypress handler registration on error.
|
|
1170
|
+
* Removes handler, clears instance from shared state, and restores raw mode if needed.
|
|
1171
|
+
*
|
|
1172
|
+
* @param shared - The shared state object
|
|
1173
|
+
* @param didAttemptRawModeEnable - If true, we attempted to enable raw mode (even if ownership wasn't recorded).
|
|
1174
|
+
* This handles the edge case where setRawMode(true) throws after actually enabling raw mode.
|
|
1175
|
+
*/
|
|
1176
|
+
cleanupKeypressHandler(shared, didAttemptRawModeEnable = false) {
|
|
1177
|
+
if (this.keypressHandler) {
|
|
1178
|
+
try {
|
|
1179
|
+
process.stdin.off("keypress", this.keypressHandler);
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
this.keypressHandler = void 0;
|
|
1183
|
+
}
|
|
1184
|
+
shared.attachedInstances.delete(this.instanceID);
|
|
1185
|
+
if (didAttemptRawModeEnable && process.stdin.isTTY && process.stdin.isRaw) {
|
|
1186
|
+
shared.rawModeEnabledByManager = true;
|
|
1187
|
+
if (shared.rawModeOwner === null) {
|
|
1188
|
+
shared.rawModeOwner = this.instanceID;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (shared.rawModeEnabledByManager && shared.rawModeOwner === this.instanceID && shared.attachedInstances.size > 0) {
|
|
1192
|
+
transferRawModeOwnership(shared, this.instanceID);
|
|
1193
|
+
} else if (shared.rawModeOwner !== null && shared.attachedInstances.size > 0 && shared.rawModeEnabledByManager && !shared.attachedInstances.has(shared.rawModeOwner)) {
|
|
1194
|
+
for (const candidateID of shared.attachedInstances) {
|
|
1195
|
+
if (shared.attachedInstances.has(candidateID)) {
|
|
1196
|
+
shared.rawModeOwner = candidateID;
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
const shouldRestoreRawMode = shared.attachedInstances.size === 0 && shared.rawModeEnabledByManager && (shared.rawModeOwner === this.instanceID || didAttemptRawModeEnable && shared.rawModeOwner === null);
|
|
1202
|
+
if (shouldRestoreRawMode) {
|
|
1203
|
+
try {
|
|
1204
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
1205
|
+
process.stdin.setRawMode(false);
|
|
1206
|
+
}
|
|
1207
|
+
shared.rawModeOwner = null;
|
|
1208
|
+
shared.rawModeEnabledByManager = false;
|
|
1209
|
+
} catch {
|
|
1210
|
+
if (didAttemptRawModeEnable && shared.rawModeOwner === null) {
|
|
1211
|
+
shared.rawModeOwner = this.instanceID;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Restore stdin to normal mode and clean up keypress listener.
|
|
1218
|
+
* Uses remove-then-check pattern (mirror of add-then-check in attach):
|
|
1219
|
+
* 1. Remove ourselves from attachedInstances first
|
|
1220
|
+
* 2. Check if we're the last (size === 0) to disable raw mode and pause stdin
|
|
1221
|
+
*
|
|
1222
|
+
* Note: Can be called even if keypressHandler is undefined (e.g., during error recovery).
|
|
1223
|
+
* In that case, we still update shared state and attempt terminal restoration if we
|
|
1224
|
+
* were the recorded raw mode owner.
|
|
1225
|
+
*/
|
|
1226
|
+
restoreStdin() {
|
|
1227
|
+
const shared = getSharedState();
|
|
1228
|
+
if (this.keypressHandler) {
|
|
1229
|
+
try {
|
|
1230
|
+
process.stdin.off("keypress", this.keypressHandler);
|
|
1231
|
+
} catch {
|
|
1232
|
+
}
|
|
1233
|
+
this.keypressHandler = void 0;
|
|
1234
|
+
}
|
|
1235
|
+
shared.attachedInstances.delete(this.instanceID);
|
|
1236
|
+
const isLastInstance = shared.attachedInstances.size === 0;
|
|
1237
|
+
const isCurrentOwner = shared.rawModeOwner === this.instanceID;
|
|
1238
|
+
if (!isLastInstance && isCurrentOwner && shared.rawModeEnabledByManager) {
|
|
1239
|
+
transferRawModeOwnership(shared, this.instanceID);
|
|
1240
|
+
} else if (!isLastInstance && shared.rawModeOwner !== null && shared.rawModeEnabledByManager && !shared.attachedInstances.has(shared.rawModeOwner)) {
|
|
1241
|
+
for (const candidateID of shared.attachedInstances) {
|
|
1242
|
+
if (shared.attachedInstances.has(candidateID)) {
|
|
1243
|
+
shared.rawModeOwner = candidateID;
|
|
1244
|
+
break;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (isLastInstance && shared.rawModeOwner === this.instanceID && shared.rawModeEnabledByManager) {
|
|
1249
|
+
try {
|
|
1250
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
1251
|
+
process.stdin.setRawMode(false);
|
|
1252
|
+
}
|
|
1253
|
+
shared.rawModeOwner = null;
|
|
1254
|
+
shared.rawModeEnabledByManager = false;
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (isLastInstance) {
|
|
1259
|
+
try {
|
|
1260
|
+
process.stdin.pause();
|
|
1261
|
+
} catch {
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
export {
|
|
1267
|
+
ProcessSignalManager
|
|
1268
|
+
};
|
|
1269
|
+
//# sourceMappingURL=process-signal-manager.js.map
|