@cyclonedx/cdxgen 12.2.0 → 12.2.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/README.md +5 -2
- package/bin/cdxgen.js +19 -1
- package/lib/cli/index.js +122 -57
- package/lib/cli/index.poku.js +117 -0
- package/lib/helpers/analyzer.js +606 -3
- package/lib/helpers/analyzer.poku.js +230 -0
- package/lib/helpers/depsUtils.js +16 -0
- package/lib/helpers/depsUtils.poku.js +58 -1
- package/lib/helpers/display.js +4 -2
- package/lib/helpers/remote/dependency-track.js +84 -0
- package/lib/helpers/remote/dependency-track.poku.js +119 -0
- package/lib/helpers/table.js +384 -0
- package/lib/helpers/table.poku.js +186 -0
- package/lib/helpers/utils.js +184 -10
- package/lib/helpers/utils.poku.js +118 -11
- package/lib/server/openapi.yaml +33 -0
- package/lib/server/server.js +10 -2
- package/lib/server/server.poku.js +209 -0
- package/lib/stages/postgen/auditBom.js +1 -2
- package/lib/validator/reporters/console.js +2 -2
- package/package.json +1 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/helpers/analyzer.d.ts.map +1 -1
- package/types/lib/helpers/depsUtils.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +16 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- package/types/lib/helpers/table.d.ts +6 -0
- package/types/lib/helpers/table.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +1 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { TABLE_BORDER_STYLE } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const ANSI_PATTERN = "\\u001B\\[[0-?]*[ -/]*[@-~]";
|
|
6
|
+
const ANSI_REGEX = new RegExp(ANSI_PATTERN, "g");
|
|
7
|
+
const COMBINING_MARK_REGEX = /\p{Mark}/u;
|
|
8
|
+
const BORDER_STYLES = {
|
|
9
|
+
ascii: {
|
|
10
|
+
bottomJoin: "+",
|
|
11
|
+
bottomLeft: "+",
|
|
12
|
+
bottomRight: "+",
|
|
13
|
+
horizontal: "-",
|
|
14
|
+
midJoin: "+",
|
|
15
|
+
midLeft: "+",
|
|
16
|
+
midRight: "+",
|
|
17
|
+
topJoin: "+",
|
|
18
|
+
topLeft: "+",
|
|
19
|
+
topRight: "+",
|
|
20
|
+
vertical: "|",
|
|
21
|
+
},
|
|
22
|
+
unicode: {
|
|
23
|
+
bottomJoin: "┴",
|
|
24
|
+
bottomLeft: "└",
|
|
25
|
+
bottomRight: "┘",
|
|
26
|
+
horizontal: "─",
|
|
27
|
+
midJoin: "┼",
|
|
28
|
+
midLeft: "├",
|
|
29
|
+
midRight: "┤",
|
|
30
|
+
topJoin: "┬",
|
|
31
|
+
topLeft: "┌",
|
|
32
|
+
topRight: "┐",
|
|
33
|
+
vertical: "│",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const stripAnsi = (input) => `${input ?? ""}`.replace(ANSI_REGEX, "");
|
|
38
|
+
|
|
39
|
+
const isFullWidthCodePoint = (codePoint) => {
|
|
40
|
+
return (
|
|
41
|
+
codePoint >= 0x1100 &&
|
|
42
|
+
(codePoint <= 0x115f ||
|
|
43
|
+
codePoint === 0x2329 ||
|
|
44
|
+
codePoint === 0x232a ||
|
|
45
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
46
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
47
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
48
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
49
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
50
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
51
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
52
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
53
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff))
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const stringWidth = (input) => {
|
|
58
|
+
const clean = stripAnsi(input);
|
|
59
|
+
let width = 0;
|
|
60
|
+
for (const char of clean) {
|
|
61
|
+
if (char === "\n" || char === "\r") {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (COMBINING_MARK_REGEX.test(char)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const codePoint = char.codePointAt(0);
|
|
68
|
+
width += isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
69
|
+
}
|
|
70
|
+
return width;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const alignText = (text, width, alignment = "left") => {
|
|
74
|
+
const visibleWidth = stringWidth(text);
|
|
75
|
+
if (visibleWidth >= width) {
|
|
76
|
+
return text;
|
|
77
|
+
}
|
|
78
|
+
const totalPad = width - visibleWidth;
|
|
79
|
+
if (alignment === "right") {
|
|
80
|
+
return `${" ".repeat(totalPad)}${text}`;
|
|
81
|
+
}
|
|
82
|
+
if (alignment === "center") {
|
|
83
|
+
const left = Math.floor(totalPad / 2);
|
|
84
|
+
const right = totalPad - left;
|
|
85
|
+
return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
|
|
86
|
+
}
|
|
87
|
+
return `${text}${" ".repeat(totalPad)}`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const splitAnsiTokens = (line) => {
|
|
91
|
+
const tokens = [];
|
|
92
|
+
const ansiRegex = new RegExp(ANSI_PATTERN, "g");
|
|
93
|
+
let cursor = 0;
|
|
94
|
+
for (const match of line.matchAll(ansiRegex)) {
|
|
95
|
+
const index = match.index ?? 0;
|
|
96
|
+
if (index > cursor) {
|
|
97
|
+
tokens.push({ isAnsi: false, value: line.slice(cursor, index) });
|
|
98
|
+
}
|
|
99
|
+
tokens.push({ isAnsi: true, value: match[0] });
|
|
100
|
+
cursor = index + match[0].length;
|
|
101
|
+
}
|
|
102
|
+
if (cursor < line.length) {
|
|
103
|
+
tokens.push({ isAnsi: false, value: line.slice(cursor) });
|
|
104
|
+
}
|
|
105
|
+
return tokens;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const wrapLineByChars = (line, width) => {
|
|
109
|
+
if (line === "") {
|
|
110
|
+
return [""];
|
|
111
|
+
}
|
|
112
|
+
if (width <= 0 || stringWidth(line) <= width) {
|
|
113
|
+
return [line];
|
|
114
|
+
}
|
|
115
|
+
const wrapped = [];
|
|
116
|
+
let chunk = "";
|
|
117
|
+
let chunkWidth = 0;
|
|
118
|
+
for (const token of splitAnsiTokens(line)) {
|
|
119
|
+
if (token.isAnsi) {
|
|
120
|
+
// Keep full ANSI sequences attached to the current chunk.
|
|
121
|
+
chunk += token.value;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
for (const char of token.value) {
|
|
125
|
+
const charWidth = stringWidth(char);
|
|
126
|
+
if (chunkWidth + charWidth > width && chunk) {
|
|
127
|
+
wrapped.push(chunk);
|
|
128
|
+
chunk = "";
|
|
129
|
+
chunkWidth = 0;
|
|
130
|
+
}
|
|
131
|
+
chunk += char;
|
|
132
|
+
chunkWidth += charWidth;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (chunk) {
|
|
136
|
+
wrapped.push(chunk);
|
|
137
|
+
}
|
|
138
|
+
return wrapped.length ? wrapped : [""];
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const wrapLineByWords = (line, width) => {
|
|
142
|
+
if (!line) {
|
|
143
|
+
return [""];
|
|
144
|
+
}
|
|
145
|
+
if (width <= 0 || stringWidth(line) <= width) {
|
|
146
|
+
return [line];
|
|
147
|
+
}
|
|
148
|
+
const words = line.split(/\s+/).filter(Boolean);
|
|
149
|
+
if (!words.length) {
|
|
150
|
+
return wrapLineByChars(line, width);
|
|
151
|
+
}
|
|
152
|
+
const wrapped = [];
|
|
153
|
+
let current = "";
|
|
154
|
+
for (const word of words) {
|
|
155
|
+
const candidate = current ? `${current} ${word}` : word;
|
|
156
|
+
if (stringWidth(candidate) <= width) {
|
|
157
|
+
current = candidate;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (current) {
|
|
161
|
+
wrapped.push(current);
|
|
162
|
+
}
|
|
163
|
+
if (stringWidth(word) > width) {
|
|
164
|
+
wrapped.push(...wrapLineByChars(word, width));
|
|
165
|
+
current = "";
|
|
166
|
+
} else {
|
|
167
|
+
current = word;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (current) {
|
|
171
|
+
wrapped.push(current);
|
|
172
|
+
}
|
|
173
|
+
return wrapped.length ? wrapped : [""];
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const wrapCellText = (text, width, wrapWord) => {
|
|
177
|
+
const normalized = `${text ?? ""}`;
|
|
178
|
+
const lines = normalized.split(/\r?\n/);
|
|
179
|
+
const wrapped = [];
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (wrapWord) {
|
|
182
|
+
wrapped.push(...wrapLineByChars(line, width));
|
|
183
|
+
} else {
|
|
184
|
+
wrapped.push(...wrapLineByWords(line, width));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return wrapped.length ? wrapped : [""];
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const getColumnCount = (rows, config = {}) => {
|
|
191
|
+
let maxCols = 0;
|
|
192
|
+
for (const row of rows) {
|
|
193
|
+
if (Array.isArray(row)) {
|
|
194
|
+
maxCols = Math.max(maxCols, row.length);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(config.columns)) {
|
|
198
|
+
maxCols = Math.max(maxCols, config.columns.length);
|
|
199
|
+
}
|
|
200
|
+
if (config.columnCount) {
|
|
201
|
+
maxCols = Math.max(maxCols, config.columnCount);
|
|
202
|
+
}
|
|
203
|
+
return maxCols;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const inferColumnWidth = (rows, columnIndex) => {
|
|
207
|
+
let maxWidth = 3;
|
|
208
|
+
for (const row of rows) {
|
|
209
|
+
const cell = row?.[columnIndex];
|
|
210
|
+
if (cell === undefined || cell === null) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const lines = `${cell}`.split(/\r?\n/);
|
|
214
|
+
for (const line of lines) {
|
|
215
|
+
maxWidth = Math.max(maxWidth, stringWidth(line));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return Math.min(maxWidth, 120);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const buildColumns = (rows, config = {}) => {
|
|
222
|
+
const columnDefault = config.columnDefault || {};
|
|
223
|
+
const columns = Array.isArray(config.columns) ? config.columns : [];
|
|
224
|
+
const count = getColumnCount(rows, config);
|
|
225
|
+
const built = [];
|
|
226
|
+
for (let i = 0; i < count; i++) {
|
|
227
|
+
const explicit = columns[i] || {};
|
|
228
|
+
const inferredWidth = inferColumnWidth(rows, i);
|
|
229
|
+
built.push({
|
|
230
|
+
alignment: explicit.alignment || columnDefault.alignment || "left",
|
|
231
|
+
width: Math.max(
|
|
232
|
+
1,
|
|
233
|
+
explicit.width || columnDefault.width || inferredWidth,
|
|
234
|
+
),
|
|
235
|
+
wrapWord: explicit.wrapWord ?? columnDefault.wrapWord ?? false,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return built;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const resolveBorderStyle = (config = {}) => {
|
|
242
|
+
const configBorderStyle = `${config.borderStyle || ""}`.toLowerCase();
|
|
243
|
+
if (configBorderStyle === "ascii" || configBorderStyle === "unicode") {
|
|
244
|
+
return configBorderStyle;
|
|
245
|
+
}
|
|
246
|
+
if (TABLE_BORDER_STYLE === "ascii" || TABLE_BORDER_STYLE === "unicode") {
|
|
247
|
+
return TABLE_BORDER_STYLE;
|
|
248
|
+
}
|
|
249
|
+
const inCI = `${process.env.CI || ""}`.toLowerCase() === "true";
|
|
250
|
+
return process.stdout?.isTTY && !inCI ? "unicode" : "ascii";
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const resolveBorderChars = (config = {}) => {
|
|
254
|
+
return BORDER_STYLES[resolveBorderStyle(config)] || BORDER_STYLES.ascii;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const drawBorder = (columns, borderChars, position = "mid") => {
|
|
258
|
+
const left =
|
|
259
|
+
position === "top"
|
|
260
|
+
? borderChars.topLeft
|
|
261
|
+
: position === "bottom"
|
|
262
|
+
? borderChars.bottomLeft
|
|
263
|
+
: borderChars.midLeft;
|
|
264
|
+
const join =
|
|
265
|
+
position === "top"
|
|
266
|
+
? borderChars.topJoin
|
|
267
|
+
: position === "bottom"
|
|
268
|
+
? borderChars.bottomJoin
|
|
269
|
+
: borderChars.midJoin;
|
|
270
|
+
const right =
|
|
271
|
+
position === "top"
|
|
272
|
+
? borderChars.topRight
|
|
273
|
+
: position === "bottom"
|
|
274
|
+
? borderChars.bottomRight
|
|
275
|
+
: borderChars.midRight;
|
|
276
|
+
return `${left}${columns.map((c) => borderChars.horizontal.repeat(c.width + 2)).join(join)}${right}`;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const renderRow = (row, columns, borderChars) => {
|
|
280
|
+
const wrappedColumns = columns.map((column, index) => {
|
|
281
|
+
return wrapCellText(row?.[index] ?? "", column.width, column.wrapWord);
|
|
282
|
+
});
|
|
283
|
+
let maxHeight = 1;
|
|
284
|
+
for (const lines of wrappedColumns) {
|
|
285
|
+
maxHeight = Math.max(maxHeight, lines.length);
|
|
286
|
+
}
|
|
287
|
+
const rendered = [];
|
|
288
|
+
for (let lineIndex = 0; lineIndex < maxHeight; lineIndex++) {
|
|
289
|
+
const columnSeparator = ` ${borderChars.vertical} `;
|
|
290
|
+
const line = columns
|
|
291
|
+
.map((column, columnIndex) => {
|
|
292
|
+
const raw = wrappedColumns[columnIndex][lineIndex] ?? "";
|
|
293
|
+
return alignText(raw, column.width, column.alignment);
|
|
294
|
+
})
|
|
295
|
+
.join(columnSeparator);
|
|
296
|
+
rendered.push(`${borderChars.vertical} ${line} ${borderChars.vertical}`);
|
|
297
|
+
}
|
|
298
|
+
return rendered;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const renderHeader = (header, columns, borderChars) => {
|
|
302
|
+
if (!header?.content) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
const contentAlignment = header.alignment || "left";
|
|
306
|
+
const totalWidth =
|
|
307
|
+
columns.reduce((sum, c) => sum + c.width, 0) + (columns.length - 1) * 3;
|
|
308
|
+
const headerLines = `${header.content}`.split(/\r?\n/);
|
|
309
|
+
const rendered = [];
|
|
310
|
+
for (const line of headerLines) {
|
|
311
|
+
const wrapped = wrapLineByChars(line, totalWidth);
|
|
312
|
+
for (const wrappedLine of wrapped) {
|
|
313
|
+
rendered.push(
|
|
314
|
+
`${borderChars.vertical} ${alignText(wrappedLine, totalWidth, contentAlignment)} ${borderChars.vertical}`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return rendered;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const formatTable = (rows, config = {}) => {
|
|
322
|
+
if (!rows?.length) {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
const columns = buildColumns(rows, config);
|
|
326
|
+
const borderChars = resolveBorderChars(config);
|
|
327
|
+
const topBorder = drawBorder(columns, borderChars, "top");
|
|
328
|
+
const middleBorder = drawBorder(columns, borderChars, "mid");
|
|
329
|
+
const bottomBorder = drawBorder(columns, borderChars, "bottom");
|
|
330
|
+
const output = [topBorder];
|
|
331
|
+
const headerLines = renderHeader(config.header, columns, borderChars);
|
|
332
|
+
if (headerLines.length) {
|
|
333
|
+
output.push(...headerLines);
|
|
334
|
+
output.push(middleBorder);
|
|
335
|
+
}
|
|
336
|
+
for (let i = 0; i < rows.length; i++) {
|
|
337
|
+
output.push(...renderRow(rows[i], columns, borderChars));
|
|
338
|
+
output.push(i < rows.length - 1 ? middleBorder : bottomBorder);
|
|
339
|
+
}
|
|
340
|
+
return output.join("\n");
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
export function table(rows, config = {}) {
|
|
344
|
+
return formatTable(rows, config);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function createStream(config = {}) {
|
|
348
|
+
let columns;
|
|
349
|
+
let middleBorder;
|
|
350
|
+
let bottomBorder;
|
|
351
|
+
let hasRows = false;
|
|
352
|
+
let closed = false;
|
|
353
|
+
const borderChars = resolveBorderChars(config);
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
write(row) {
|
|
357
|
+
if (closed) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!columns) {
|
|
361
|
+
const seedRows = Array.isArray(row) ? [row] : [[row]];
|
|
362
|
+
columns = buildColumns(seedRows, config);
|
|
363
|
+
const topBorder = drawBorder(columns, borderChars, "top");
|
|
364
|
+
middleBorder = drawBorder(columns, borderChars, "mid");
|
|
365
|
+
bottomBorder = drawBorder(columns, borderChars, "bottom");
|
|
366
|
+
process.stdout.write(`${topBorder}\n`);
|
|
367
|
+
}
|
|
368
|
+
if (hasRows) {
|
|
369
|
+
process.stdout.write(`${middleBorder}\n`);
|
|
370
|
+
}
|
|
371
|
+
const safeRow = Array.isArray(row) ? row : [row];
|
|
372
|
+
const rendered = renderRow(safeRow, columns, borderChars);
|
|
373
|
+
process.stdout.write(`${rendered.join("\n")}\n`);
|
|
374
|
+
hasRows = true;
|
|
375
|
+
},
|
|
376
|
+
end() {
|
|
377
|
+
if (!columns || closed) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
process.stdout.write(`${bottomBorder}\n`);
|
|
381
|
+
closed = true;
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import esmock from "esmock";
|
|
4
|
+
import { assert, describe, it } from "poku";
|
|
5
|
+
import sinon from "sinon";
|
|
6
|
+
|
|
7
|
+
import { createStream, table } from "./table.js";
|
|
8
|
+
|
|
9
|
+
const withStdoutTTY = (ttyValue, action) => {
|
|
10
|
+
const descriptor = Object.getOwnPropertyDescriptor(process.stdout, "isTTY");
|
|
11
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
12
|
+
configurable: true,
|
|
13
|
+
enumerable: true,
|
|
14
|
+
value: ttyValue,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
action();
|
|
19
|
+
} finally {
|
|
20
|
+
if (descriptor) {
|
|
21
|
+
Object.defineProperty(process.stdout, "isTTY", descriptor);
|
|
22
|
+
} else {
|
|
23
|
+
delete process.stdout.isTTY;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("table()", () => {
|
|
29
|
+
it("renders headers, rows, and borders", () => {
|
|
30
|
+
const output = table(
|
|
31
|
+
[
|
|
32
|
+
["Name", "Score"],
|
|
33
|
+
["alpha", "100"],
|
|
34
|
+
],
|
|
35
|
+
{
|
|
36
|
+
borderStyle: "ascii",
|
|
37
|
+
columns: [{ width: 8 }, { width: 5, alignment: "right" }],
|
|
38
|
+
header: { alignment: "center", content: "Report" },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.ok(output.includes("Report"));
|
|
43
|
+
assert.ok(output.includes("alpha"));
|
|
44
|
+
assert.ok(output.includes(" 100"));
|
|
45
|
+
assert.ok(output.includes("+----------+-------+"));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("wraps long words when wrapWord is enabled", () => {
|
|
49
|
+
const output = table([["A", "supercalifragilistic"]], {
|
|
50
|
+
borderStyle: "ascii",
|
|
51
|
+
columns: [{ width: 2 }, { width: 6, wrapWord: true }],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
assert.ok(output.includes("superc"));
|
|
55
|
+
assert.ok(output.includes("alifra"));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves ANSI escape sequences while wrapping by characters", () => {
|
|
59
|
+
const output = table([["\x1b[1;35mabcdef\x1b[0m"]], {
|
|
60
|
+
borderStyle: "ascii",
|
|
61
|
+
columns: [{ width: 4, wrapWord: true }],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// biome-ignore lint/complexity/useRegexLiterals: avoid control-character regex literal warnings for ANSI pattern.
|
|
65
|
+
const ansiRegex = new RegExp("\\u001B\\[[0-?]*[ -/]*[@-~]", "g");
|
|
66
|
+
const ansiMatches = output.match(ansiRegex) || [];
|
|
67
|
+
assert.strictEqual(ansiMatches.length, 2);
|
|
68
|
+
assert.ok(output.includes("abcd"));
|
|
69
|
+
assert.ok(output.includes("ef"));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("keeps falsy values like 0 and false in cells", () => {
|
|
73
|
+
const output = table([[0, false]], {
|
|
74
|
+
borderStyle: "ascii",
|
|
75
|
+
columns: [{ width: 3 }, { width: 5 }],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
assert.ok(output.includes(" 0 "));
|
|
79
|
+
assert.ok(output.includes("false"));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("uses unicode borders in auto mode on tty when not in CI", () => {
|
|
83
|
+
const originalCI = process.env.CI;
|
|
84
|
+
delete process.env.CI;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
withStdoutTTY(true, () => {
|
|
88
|
+
const output = table([["x"]], {
|
|
89
|
+
borderStyle: "auto",
|
|
90
|
+
columns: [{ width: 3 }],
|
|
91
|
+
});
|
|
92
|
+
assert.ok(output.includes("┌"));
|
|
93
|
+
assert.ok(output.includes("│"));
|
|
94
|
+
});
|
|
95
|
+
} finally {
|
|
96
|
+
if (originalCI === undefined) {
|
|
97
|
+
delete process.env.CI;
|
|
98
|
+
} else {
|
|
99
|
+
process.env.CI = originalCI;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("uses ascii borders in auto mode when CI=true", () => {
|
|
105
|
+
const originalCI = process.env.CI;
|
|
106
|
+
process.env.CI = "true";
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
withStdoutTTY(true, () => {
|
|
110
|
+
const output = table([["x"]], {
|
|
111
|
+
borderStyle: "auto",
|
|
112
|
+
columns: [{ width: 3 }],
|
|
113
|
+
});
|
|
114
|
+
assert.ok(output.includes("+"));
|
|
115
|
+
assert.ok(output.includes("|"));
|
|
116
|
+
assert.ok(!output.includes("┌"));
|
|
117
|
+
});
|
|
118
|
+
} finally {
|
|
119
|
+
if (originalCI === undefined) {
|
|
120
|
+
delete process.env.CI;
|
|
121
|
+
} else {
|
|
122
|
+
process.env.CI = originalCI;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("uses TABLE_BORDER_STYLE=unicode from utils even when not tty", async () => {
|
|
128
|
+
const { table: tableWithUnicode } = await esmock("./table.js", {
|
|
129
|
+
"./utils.js": { TABLE_BORDER_STYLE: "unicode" },
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
withStdoutTTY(false, () => {
|
|
133
|
+
const output = tableWithUnicode([["x"]], {
|
|
134
|
+
columns: [{ width: 3 }],
|
|
135
|
+
});
|
|
136
|
+
assert.ok(output.includes("┌"));
|
|
137
|
+
assert.ok(output.includes("│"));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("falls back to auto-detect when TABLE_BORDER_STYLE is auto", async () => {
|
|
142
|
+
const { table: tableWithAuto } = await esmock("./table.js", {
|
|
143
|
+
"./utils.js": { TABLE_BORDER_STYLE: "auto" },
|
|
144
|
+
});
|
|
145
|
+
const originalCI = process.env.CI;
|
|
146
|
+
process.env.CI = "true";
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
withStdoutTTY(true, () => {
|
|
150
|
+
const output = tableWithAuto([["x"]], {
|
|
151
|
+
columns: [{ width: 3 }],
|
|
152
|
+
});
|
|
153
|
+
assert.ok(output.includes("+"));
|
|
154
|
+
assert.ok(output.includes("|"));
|
|
155
|
+
});
|
|
156
|
+
} finally {
|
|
157
|
+
if (originalCI === undefined) {
|
|
158
|
+
delete process.env.CI;
|
|
159
|
+
} else {
|
|
160
|
+
process.env.CI = originalCI;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("createStream()", () => {
|
|
167
|
+
it("writes rows incrementally to stdout and closes with a bottom border", () => {
|
|
168
|
+
const writeStub = sinon.stub(process.stdout, "write");
|
|
169
|
+
const stream = createStream({
|
|
170
|
+
borderStyle: "unicode",
|
|
171
|
+
columns: [{ width: 5 }, { width: 5 }],
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
stream.write(["h1", "h2"]);
|
|
175
|
+
stream.write(["v1", "v2"]);
|
|
176
|
+
stream.end();
|
|
177
|
+
|
|
178
|
+
assert.ok(writeStub.callCount >= 3);
|
|
179
|
+
assert.ok(writeStub.calledWithMatch("h1"));
|
|
180
|
+
assert.ok(writeStub.calledWithMatch("v2"));
|
|
181
|
+
const output = writeStub.args.map((args) => args[0]).join("");
|
|
182
|
+
assert.ok(output.includes("├"));
|
|
183
|
+
assert.ok(output.trimEnd().endsWith("┘"));
|
|
184
|
+
writeStub.restore();
|
|
185
|
+
});
|
|
186
|
+
});
|