@cdxgen/cdxgen 13.0.0 → 13.1.0
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 +26 -13
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +140 -8
- package/bin/convert.js +7 -0
- package/bin/evinse.js +1 -1
- package/bin/hbom.js +13 -4
- package/bin/tracebom.js +9 -1
- package/data/component-tags.json +8 -1
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1144 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/cli/bomAssembly.js +83 -15
- package/lib/cli/cliOptions.js +124 -7
- package/lib/cli/index.js +76 -48
- package/lib/cli/jsBom.js +32 -3
- package/lib/cli/jvmBom.js +1494 -1315
- package/lib/cli/managedBom.js +97 -28
- package/lib/cli/nativeBom.js +189 -10
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +69 -1
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +76 -25
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/ui.js +46 -2
- package/lib/ecosystems/ecosystems.js +9 -0
- package/lib/ecosystems/gradleutils.js +355 -15
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-misc.js +16 -2
- package/lib/ecosystems/parsers-python.js +171 -35
- package/lib/ecosystems/parsers-rust.js +12 -2
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/utils.js +9 -1
- package/lib/evinser/evinser.js +30 -12
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/auditCategories.js +9 -0
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +431 -22
- package/lib/inventory/envcontext.js +211 -19
- package/lib/inventory/fetchBatch.js +57 -0
- package/lib/inventory/jvmToolEnv.js +593 -0
- package/lib/inventory/osinfo.js +123 -16
- package/lib/inventory/table.js +9 -5
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +216 -40
- package/lib/managers/docker.js +26 -5
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +227 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +115 -2
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +14 -14
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +14 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +14 -1
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +1 -1
- package/types/lib/core/ui.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +33 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +20 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/utils.d.ts +4 -4
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +5 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +8 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +20 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +9 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +83 -2
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/table.d.ts +4 -1
- package/types/lib/inventory/table.d.ts.map +1 -1
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +71 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +2 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/lib/inventory/osinfo.js
CHANGED
|
@@ -86,6 +86,112 @@ export const OS_DISTRO_ALIAS = {
|
|
|
86
86
|
"red hat enterprise linux 10": "rhel-10",
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Canonical purl namespace for distro vendors that are published under more
|
|
91
|
+
* than one spelling. Both the os-release path (`getDistroInfo`) and the trivy
|
|
92
|
+
* passthrough in `lib/managers/binary.js` route through this single map so the
|
|
93
|
+
* two paths cannot drift apart again.
|
|
94
|
+
*
|
|
95
|
+
* Azure Linux 2.0 is CBL-Mariner 2.0 — Microsoft renamed the product — and the
|
|
96
|
+
* OSV feed publishes `pkg:rpm/azure-linux/<name>` for every generation
|
|
97
|
+
* (ecosystems "Azure Linux:2" and "Azure Linux:3"), so the historical
|
|
98
|
+
* spellings azurelinux, cbl-mariner and mariner all canonicalise to
|
|
99
|
+
* "azure-linux". rhel/ol/amzn/opensuse-* are the cases previously inlined in
|
|
100
|
+
* getDistroInfo(); alma/rocky-linux were previously inlined in binary.js.
|
|
101
|
+
*
|
|
102
|
+
* Amazon Linux canonicalises to "amazon", the spelling the vulnerability feeds
|
|
103
|
+
* publish (AppThreat vuln-list `amazon`): the os-release ID is `amzn` and
|
|
104
|
+
* trivy emits `amazon`, and the previous `amazonlinux` mapping here matched
|
|
105
|
+
* neither, so live-host Amazon Linux purls matched nothing at all.
|
|
106
|
+
*/
|
|
107
|
+
export const OS_NAMESPACE_ALIAS = {
|
|
108
|
+
alma: "almalinux",
|
|
109
|
+
amazonlinux: "amazon",
|
|
110
|
+
amzn: "amazon",
|
|
111
|
+
azurelinux: "azure-linux",
|
|
112
|
+
"cbl-mariner": "azure-linux",
|
|
113
|
+
mariner: "azure-linux",
|
|
114
|
+
ol: "oracle",
|
|
115
|
+
"opensuse-leap": "opensuse",
|
|
116
|
+
"opensuse-tumbleweed": "opensuse",
|
|
117
|
+
rhel: "redhat",
|
|
118
|
+
"rocky-linux": "rocky",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Vendor prefixes that `distro` qualifier values are canonicalised for, so one
|
|
123
|
+
* release has exactly one qualifier value no matter which side produced it.
|
|
124
|
+
*
|
|
125
|
+
* trivy stamps the qualifier from its own distro table while cdxgen stamps it
|
|
126
|
+
* from os-release, and the two disagree: an Oracle Linux 9.8 image came out
|
|
127
|
+
* with `oracle-9.8` on 99 components and `ol-9.8` on 43, and a RHEL 9.8 image
|
|
128
|
+
* with namespace `redhat` but qualifier `rhel-9.8`. Canonicalising both sides
|
|
129
|
+
* through this map collapses each release onto the vendor its purl namespace
|
|
130
|
+
* already uses.
|
|
131
|
+
*
|
|
132
|
+
* SUSE is deliberately absent. `sles-15.6`, `opensuse-leap-15.6` and
|
|
133
|
+
* `opensuse-tumbleweed-<snapshot>` are the values consumers derive the release
|
|
134
|
+
* channel from (vulnerability-db maps them to per-release channels), and both
|
|
135
|
+
* sides already agree on them; folding Leap and Tumbleweed onto a bare
|
|
136
|
+
* `opensuse-` vendor would destroy exactly the distinction that matters.
|
|
137
|
+
*
|
|
138
|
+
* Keys are matched longest-first so `cbl-mariner-2.0` resolves against the
|
|
139
|
+
* full vendor segment rather than a shorter prefix.
|
|
140
|
+
*/
|
|
141
|
+
export const OS_DISTRO_QUALIFIER_ALIAS = {
|
|
142
|
+
alma: "almalinux",
|
|
143
|
+
amazonlinux: "amazon",
|
|
144
|
+
amzn: "amazon",
|
|
145
|
+
azurelinux: "azure-linux",
|
|
146
|
+
"cbl-mariner": "azure-linux",
|
|
147
|
+
mariner: "azure-linux",
|
|
148
|
+
ol: "oracle",
|
|
149
|
+
rhel: "redhat",
|
|
150
|
+
"rocky-linux": "rocky",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// A distro qualifier reaches a purl verbatim, so anything outside the purl
|
|
154
|
+
// qualifier charset makes the whole purl — and with it the whole BOM —
|
|
155
|
+
// invalid. trivy emits Amazon Linux as
|
|
156
|
+
// `amazon-2023.12.20260727+(Amazon Linux)`, which failed cdxgen's own schema
|
|
157
|
+
// validation and cost the entire SBOM.
|
|
158
|
+
const SAFE_DISTRO_QUALIFIER = /^[A-Za-z0-9._-]+$/;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Whether a `distro` qualifier value is safe to place in a purl unencoded.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} qualifier - distro qualifier value
|
|
164
|
+
* @returns {boolean} true when the value contains only purl-safe characters
|
|
165
|
+
*/
|
|
166
|
+
export function isCleanDistroQualifier(qualifier) {
|
|
167
|
+
return !!qualifier && SAFE_DISTRO_QUALIFIER.test(qualifier);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const OS_DISTRO_QUALIFIER_ALIAS_KEYS = Object.keys(
|
|
171
|
+
OS_DISTRO_QUALIFIER_ALIAS,
|
|
172
|
+
).sort((a, b) => b.length - a.length);
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Canonicalise the vendor prefix of a `distro` qualifier value
|
|
176
|
+
* (e.g. "cbl-mariner-2.0" or "mariner-2.0" -> "azure-linux-2.0") so a release
|
|
177
|
+
* has exactly one qualifier spelling regardless of which tool emitted it.
|
|
178
|
+
* Values without a known vendor prefix are returned unchanged.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} qualifier - distro qualifier value such as "azurelinux-3.0"
|
|
181
|
+
* @returns {string} canonical qualifier value
|
|
182
|
+
*/
|
|
183
|
+
export function canonicalDistroQualifier(qualifier) {
|
|
184
|
+
if (!qualifier) {
|
|
185
|
+
return qualifier;
|
|
186
|
+
}
|
|
187
|
+
for (const key of OS_DISTRO_QUALIFIER_ALIAS_KEYS) {
|
|
188
|
+
if (qualifier.startsWith(`${key}-`)) {
|
|
189
|
+
return `${OS_DISTRO_QUALIFIER_ALIAS[key]}${qualifier.slice(key.length)}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return qualifier;
|
|
193
|
+
}
|
|
194
|
+
|
|
89
195
|
// ---------------------------------------------------------------------------
|
|
90
196
|
// Raw os-release cache — keyed by the osRelease file path so the live-host
|
|
91
197
|
// cache (/etc/os-release) and any container rootfs path are kept separate.
|
|
@@ -151,8 +257,10 @@ export function _resetOsReleaseCache() {
|
|
|
151
257
|
*
|
|
152
258
|
* Returns an object with:
|
|
153
259
|
* - purlType {string} "deb" | "apk" | "rpm"
|
|
154
|
-
* - namespace {string} purl namespace (e.g. "ubuntu", "alpine",
|
|
155
|
-
*
|
|
260
|
+
* - namespace {string} canonical purl namespace (e.g. "ubuntu", "alpine",
|
|
261
|
+
* "azure-linux" for both Azure Linux and CBL-Mariner)
|
|
262
|
+
* - distroId {string} ID + "-" + VERSION_ID with a canonical vendor
|
|
263
|
+
* prefix (e.g. "ubuntu-22.04", "azure-linux-3.0")
|
|
156
264
|
* - distroName {string} codename/alias (e.g. "jammy")
|
|
157
265
|
*
|
|
158
266
|
* Mirrors the logic in lib/managers/binary.js getOSPackages() so that both
|
|
@@ -180,6 +288,7 @@ export function getDistroInfo(root = "/") {
|
|
|
180
288
|
purlType = "deb";
|
|
181
289
|
break;
|
|
182
290
|
case "alpine":
|
|
291
|
+
case "alpaquita":
|
|
183
292
|
case "openwrt":
|
|
184
293
|
purlType = "apk";
|
|
185
294
|
break;
|
|
@@ -193,6 +302,9 @@ export function getDistroInfo(root = "/") {
|
|
|
193
302
|
case "centos":
|
|
194
303
|
case "rocky":
|
|
195
304
|
case "almalinux":
|
|
305
|
+
case "azurelinux":
|
|
306
|
+
case "cbl-mariner":
|
|
307
|
+
case "mariner":
|
|
196
308
|
case "oracle":
|
|
197
309
|
case "ol":
|
|
198
310
|
case "amzn":
|
|
@@ -216,19 +328,10 @@ export function getDistroInfo(root = "/") {
|
|
|
216
328
|
break;
|
|
217
329
|
}
|
|
218
330
|
|
|
219
|
-
// Determine the purl namespace (vendor)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} else if (rawId === "ol") {
|
|
224
|
-
namespace = "oracle";
|
|
225
|
-
} else if (rawId === "amzn") {
|
|
226
|
-
namespace = "amazonlinux";
|
|
227
|
-
} else if (rawId === "opensuse-leap" || rawId === "opensuse-tumbleweed") {
|
|
228
|
-
namespace = "opensuse";
|
|
229
|
-
} else if (rawId === "almalinux") {
|
|
230
|
-
namespace = "almalinux";
|
|
231
|
-
} else if (!rawId) {
|
|
331
|
+
// Determine the purl namespace (vendor), canonicalising vendors that are
|
|
332
|
+
// published under several spellings (e.g. mariner -> azure-linux)
|
|
333
|
+
let namespace = OS_NAMESPACE_ALIAS[rawId] || rawId;
|
|
334
|
+
if (!rawId) {
|
|
232
335
|
// Fallback via ID_LIKE
|
|
233
336
|
if (idLike.includes("rhel") || idLike.includes("centos")) {
|
|
234
337
|
namespace = "redhat";
|
|
@@ -245,8 +348,12 @@ export function getDistroInfo(root = "/") {
|
|
|
245
348
|
}
|
|
246
349
|
}
|
|
247
350
|
|
|
248
|
-
// Build distroId = ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17")
|
|
351
|
+
// Build distroId = ID-VERSION_ID (e.g. "fedora-25", "alpine-3.17"),
|
|
352
|
+
// canonicalising the vendor prefix so one release has a single qualifier
|
|
353
|
+
// value (e.g. "mariner-2.0" and "azurelinux-3.0" -> "azure-linux-2.0"/
|
|
354
|
+
// "azure-linux-3.0")
|
|
249
355
|
let distroId = versionId ? `${rawId}-${versionId}` : rawId;
|
|
356
|
+
distroId = canonicalDistroQualifier(distroId);
|
|
250
357
|
|
|
251
358
|
// Special-case Alpine: truncate to major.minor
|
|
252
359
|
if (purlType === "apk" && versionId) {
|
package/lib/inventory/table.js
CHANGED
|
@@ -2,6 +2,7 @@ import process from "node:process";
|
|
|
2
2
|
|
|
3
3
|
import { readEnvironmentVariable } from "../core/activity.js";
|
|
4
4
|
import { TABLE_BORDER_STYLE } from "../core/env.js";
|
|
5
|
+
import { diagnosticStream } from "../core/logger.js";
|
|
5
6
|
import {
|
|
6
7
|
alignText,
|
|
7
8
|
stringWidth,
|
|
@@ -218,7 +219,10 @@ export function table(rows, config = {}) {
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
/**
|
|
221
|
-
* Create a streaming table writer that renders rows
|
|
222
|
+
* Create a streaming table writer that renders rows incrementally.
|
|
223
|
+
*
|
|
224
|
+
* Rows go to the diagnostic stream: a rendered table is human-readable output,
|
|
225
|
+
* and writing it to stdout corrupted the BOM document under `-o -`.
|
|
222
226
|
*
|
|
223
227
|
* @param {Object} [config={}] Table configuration (header, columns, border style)
|
|
224
228
|
* @returns {{write(row: *): void, end(): void}} Writer whose `end()` emits the bottom border
|
|
@@ -242,21 +246,21 @@ export function createStream(config = {}) {
|
|
|
242
246
|
const topBorder = drawBorder(columns, borderChars, "top");
|
|
243
247
|
middleBorder = drawBorder(columns, borderChars, "mid");
|
|
244
248
|
bottomBorder = drawBorder(columns, borderChars, "bottom");
|
|
245
|
-
|
|
249
|
+
diagnosticStream.write(`${topBorder}\n`);
|
|
246
250
|
}
|
|
247
251
|
if (hasRows) {
|
|
248
|
-
|
|
252
|
+
diagnosticStream.write(`${middleBorder}\n`);
|
|
249
253
|
}
|
|
250
254
|
const safeRow = Array.isArray(row) ? row : [row];
|
|
251
255
|
const rendered = renderRow(safeRow, columns, borderChars);
|
|
252
|
-
|
|
256
|
+
diagnosticStream.write(`${rendered.join("\n")}\n`);
|
|
253
257
|
hasRows = true;
|
|
254
258
|
},
|
|
255
259
|
end() {
|
|
256
260
|
if (!columns || closed) {
|
|
257
261
|
return;
|
|
258
262
|
}
|
|
259
|
-
|
|
263
|
+
diagnosticStream.write(`${bottomBorder}\n`);
|
|
260
264
|
closed = true;
|
|
261
265
|
},
|
|
262
266
|
};
|