altium-toolkit 1.0.8 → 1.0.10

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.
Files changed (102) hide show
  1. package/README.md +18 -6
  2. package/docs/api.md +78 -16
  3. package/docs/model-format.md +229 -8
  4. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
  5. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
  6. package/docs/schemas/altium_toolkit/netlist_a1.schema.json +53 -0
  7. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +1826 -110
  8. package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
  9. package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +86 -0
  10. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +63 -0
  11. package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
  12. package/docs/schemas/altium_toolkit/schematic_svg_semantics_a1.schema.json +50 -0
  13. package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
  14. package/docs/testing.md +9 -3
  15. package/package.json +1 -1
  16. package/spec/library-scope.md +7 -1
  17. package/src/core/altium/AltiumLayoutParser.mjs +104 -8
  18. package/src/core/altium/AltiumParser.mjs +196 -45
  19. package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
  20. package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
  21. package/src/core/altium/EmbeddedFileInventoryBuilder.mjs +255 -0
  22. package/src/core/altium/IntLibModelParser.mjs +240 -0
  23. package/src/core/altium/IntLibStreamExtractor.mjs +366 -0
  24. package/src/core/altium/LibraryRenderManifestBuilder.mjs +417 -0
  25. package/src/core/altium/LibrarySearchIndex.mjs +215 -0
  26. package/src/core/altium/NormalizedModelSchema.mjs +36 -0
  27. package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
  28. package/src/core/altium/PcbCustomPadShapeParser.mjs +244 -0
  29. package/src/core/altium/PcbDefaultsParser.mjs +171 -0
  30. package/src/core/altium/PcbDimensionParser.mjs +229 -0
  31. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +232 -6
  32. package/src/core/altium/PcbExtendedPrimitiveInformationParser.mjs +256 -0
  33. package/src/core/altium/PcbLibModelParser.mjs +235 -14
  34. package/src/core/altium/PcbLibStreamExtractor.mjs +62 -4
  35. package/src/core/altium/PcbMaskPasteResolver.mjs +354 -0
  36. package/src/core/altium/PcbMechanicalLayerPairParser.mjs +204 -0
  37. package/src/core/altium/PcbModelParser.mjs +495 -32
  38. package/src/core/altium/PcbOwnershipGraphBuilder.mjs +245 -0
  39. package/src/core/altium/PcbPadPrimitiveParser.mjs +78 -65
  40. package/src/core/altium/PcbPadStackParser.mjs +229 -2
  41. package/src/core/altium/PcbPickPlacePositionResolver.mjs +224 -0
  42. package/src/core/altium/PcbPrimitiveParameterParser.mjs +3 -2
  43. package/src/core/altium/PcbRawRecordRegistry.mjs +121 -130
  44. package/src/core/altium/PcbRegionPrimitiveParser.mjs +76 -3
  45. package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
  46. package/src/core/altium/PcbRuleParser.mjs +354 -33
  47. package/src/core/altium/PcbSidecarRecordParser.mjs +177 -0
  48. package/src/core/altium/PcbSpecialStringResolver.mjs +220 -0
  49. package/src/core/altium/PcbStatisticsBuilder.mjs +541 -0
  50. package/src/core/altium/PcbStreamExtractor.mjs +111 -4
  51. package/src/core/altium/PcbTextPrimitiveParser.mjs +60 -0
  52. package/src/core/altium/PcbUnionParser.mjs +307 -0
  53. package/src/core/altium/PcbViaStackParser.mjs +98 -10
  54. package/src/core/altium/PcbViaStructureParser.mjs +335 -0
  55. package/src/core/altium/PrintableTextDecoder.mjs +53 -3
  56. package/src/core/altium/PrjPcbModelParser.mjs +281 -7
  57. package/src/core/altium/ProjectAnnotationParser.mjs +205 -0
  58. package/src/core/altium/ProjectDesignBundleBuilder.mjs +492 -0
  59. package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
  60. package/src/core/altium/ProjectNetlistExporter.mjs +503 -0
  61. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +109 -0
  62. package/src/core/altium/ProjectVariantViewBuilder.mjs +334 -0
  63. package/src/core/altium/SchematicBindingProvenanceParser.mjs +223 -0
  64. package/src/core/altium/SchematicComponentOwnerTextResolver.mjs +312 -0
  65. package/src/core/altium/SchematicComponentTextResolver.mjs +72 -19
  66. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +271 -0
  67. package/src/core/altium/SchematicCrossSheetConnectorParser.mjs +140 -0
  68. package/src/core/altium/SchematicDirectiveParser.mjs +312 -0
  69. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +231 -0
  70. package/src/core/altium/SchematicHarnessParser.mjs +302 -0
  71. package/src/core/altium/SchematicImageParser.mjs +474 -3
  72. package/src/core/altium/SchematicImplementationParser.mjs +518 -0
  73. package/src/core/altium/SchematicNetlistBuilder.mjs +15 -2
  74. package/src/core/altium/SchematicOwnershipGraphParser.mjs +195 -0
  75. package/src/core/altium/SchematicPinParser.mjs +84 -1
  76. package/src/core/altium/SchematicPrimitiveParser.mjs +301 -0
  77. package/src/core/altium/SchematicProjectParameterResolver.mjs +361 -0
  78. package/src/core/altium/SchematicQaReportBuilder.mjs +284 -0
  79. package/src/core/altium/SchematicRecordTypeRegistry.mjs +137 -0
  80. package/src/core/altium/SchematicRepeatedChannelParser.mjs +229 -0
  81. package/src/core/altium/SchematicStreamExtractor.mjs +10 -1
  82. package/src/core/altium/SchematicTemplateParser.mjs +256 -0
  83. package/src/core/altium/SchematicTextParser.mjs +123 -0
  84. package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
  85. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
  86. package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
  87. package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
  88. package/src/core/ole/OleCompoundDocument.mjs +20 -0
  89. package/src/parser.mjs +35 -0
  90. package/src/styles/altium-renderers.css +19 -0
  91. package/src/ui/PcbBarcodeTextRenderer.mjs +436 -0
  92. package/src/ui/PcbInteractionIndex.mjs +9 -4
  93. package/src/ui/PcbScene3dBuilder.mjs +137 -3
  94. package/src/ui/PcbScene3dModelRegistry.mjs +74 -0
  95. package/src/ui/PcbSvgRenderer.mjs +1252 -34
  96. package/src/ui/PcbTextPrimitiveRenderer.mjs +193 -7
  97. package/src/ui/SchematicNoteRenderer.mjs +9 -2
  98. package/src/ui/SchematicOwnerPinLabelLayout.mjs +206 -0
  99. package/src/ui/SchematicShapeRenderer.mjs +362 -0
  100. package/src/ui/SchematicSvgRenderer.mjs +1442 -92
  101. package/src/ui/SchematicTypography.mjs +48 -5
  102. package/src/ui/TextGeometrySidecarBuilder.mjs +147 -0
@@ -0,0 +1,255 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Builds a generic inventory for embedded payload streams.
7
+ */
8
+ export class EmbeddedFileInventoryBuilder {
9
+ static SCHEMA_ID = 'altium-toolkit.embedded-files.a1'
10
+
11
+ /**
12
+ * Builds an embedded-file inventory from a stream map.
13
+ * @param {Map<string, Uint8Array>} streams Compound-document streams.
14
+ * @param {{ skipStreamNames?: Iterable<string> }} [options] Inventory options.
15
+ * @returns {{ schema: string, files: object[], diagnostics: object[] }}
16
+ */
17
+ static buildFromStreams(streams, options = {}) {
18
+ const skipStreamNames = new Set(options.skipStreamNames || [])
19
+ const files = []
20
+ const diagnostics = []
21
+
22
+ for (const [sourceStream, bytes] of streams || []) {
23
+ if (
24
+ skipStreamNames.has(sourceStream) ||
25
+ !EmbeddedFileInventoryBuilder.#isEmbeddedPayloadStream(
26
+ sourceStream
27
+ )
28
+ ) {
29
+ continue
30
+ }
31
+
32
+ if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
33
+ diagnostics.push({
34
+ code: 'embedded-file.empty',
35
+ severity: 'warning',
36
+ sourceStream,
37
+ message: 'Embedded payload stream was empty.'
38
+ })
39
+ continue
40
+ }
41
+
42
+ files.push(
43
+ EmbeddedFileInventoryBuilder.#fileRecord(sourceStream, bytes)
44
+ )
45
+ }
46
+
47
+ return {
48
+ schema: EmbeddedFileInventoryBuilder.SCHEMA_ID,
49
+ files: files.sort((left, right) =>
50
+ left.sourceStream.localeCompare(right.sourceStream)
51
+ ),
52
+ diagnostics: diagnostics.sort((left, right) =>
53
+ left.sourceStream.localeCompare(right.sourceStream)
54
+ )
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Returns true when a stream name represents a payload-style embedded file.
60
+ * @param {string} sourceStream Stream name.
61
+ * @returns {boolean}
62
+ */
63
+ static #isEmbeddedPayloadStream(sourceStream) {
64
+ const normalized = String(sourceStream || '')
65
+ if (/\/(?:Data|Header)$/i.test(normalized)) {
66
+ return false
67
+ }
68
+
69
+ return (
70
+ /^(EmbeddedFiles|Embedded|Attachments|Images?)\//i.test(
71
+ normalized
72
+ ) ||
73
+ /^Models\/\d+$/i.test(normalized) ||
74
+ /\.[A-Za-z0-9]{2,8}$/.test(normalized)
75
+ )
76
+ }
77
+
78
+ /**
79
+ * Builds one file inventory row.
80
+ * @param {string} sourceStream Stream name.
81
+ * @param {Uint8Array} bytes Payload bytes.
82
+ * @returns {object}
83
+ */
84
+ static #fileRecord(sourceStream, bytes) {
85
+ return {
86
+ sourceStream,
87
+ name: EmbeddedFileInventoryBuilder.#basename(sourceStream),
88
+ format: EmbeddedFileInventoryBuilder.#format(sourceStream, bytes),
89
+ byteLength: bytes.byteLength,
90
+ checksum: {
91
+ algorithm: 'fnv1a32',
92
+ value: EmbeddedFileInventoryBuilder.#fnv1a32(bytes)
93
+ }
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Resolves the payload basename from a stream path.
99
+ * @param {string} sourceStream Stream name.
100
+ * @returns {string}
101
+ */
102
+ static #basename(sourceStream) {
103
+ return (
104
+ String(sourceStream || '')
105
+ .split('/')
106
+ .filter(Boolean)
107
+ .pop() || ''
108
+ )
109
+ }
110
+
111
+ /**
112
+ * Classifies payload format from extension, magic bytes, and text probes.
113
+ * @param {string} sourceStream Stream name.
114
+ * @param {Uint8Array} bytes Payload bytes.
115
+ * @returns {string}
116
+ */
117
+ static #format(sourceStream, bytes) {
118
+ const lower =
119
+ EmbeddedFileInventoryBuilder.#basename(sourceStream).toLowerCase()
120
+
121
+ if (
122
+ lower.endsWith('.png') ||
123
+ EmbeddedFileInventoryBuilder.#hasPrefix(
124
+ bytes,
125
+ [0x89, 0x50, 0x4e, 0x47]
126
+ )
127
+ ) {
128
+ return 'png'
129
+ }
130
+ if (
131
+ lower.endsWith('.jpg') ||
132
+ lower.endsWith('.jpeg') ||
133
+ EmbeddedFileInventoryBuilder.#hasPrefix(bytes, [0xff, 0xd8, 0xff])
134
+ ) {
135
+ return 'jpeg'
136
+ }
137
+ if (
138
+ lower.endsWith('.gif') ||
139
+ EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('GIF')
140
+ ) {
141
+ return 'gif'
142
+ }
143
+ if (
144
+ lower.endsWith('.bmp') ||
145
+ EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('BM')
146
+ ) {
147
+ return 'bmp'
148
+ }
149
+ if (
150
+ lower.endsWith('.svg') ||
151
+ EmbeddedFileInventoryBuilder.#trimmedText(bytes).startsWith('<svg')
152
+ ) {
153
+ return 'svg'
154
+ }
155
+ if (
156
+ lower.endsWith('.step') ||
157
+ lower.endsWith('.stp') ||
158
+ EmbeddedFileInventoryBuilder.#trimmedText(bytes).startsWith(
159
+ 'ISO-10303-21'
160
+ )
161
+ ) {
162
+ return 'step'
163
+ }
164
+ if (lower.endsWith('.sldprt') || lower.endsWith('.sldasm')) {
165
+ return 'solidworks'
166
+ }
167
+ if (lower.endsWith('.x_t') || lower.endsWith('.xmt_txt')) {
168
+ return 'parasolid-text'
169
+ }
170
+ if (lower.endsWith('.x_b') || lower.endsWith('.xmt_bin')) {
171
+ return 'parasolid-binary'
172
+ }
173
+ if (
174
+ lower.endsWith('.pdf') ||
175
+ EmbeddedFileInventoryBuilder.#asciiPrefix(bytes).startsWith('%PDF')
176
+ ) {
177
+ return 'pdf'
178
+ }
179
+ if (EmbeddedFileInventoryBuilder.#isLikelyText(bytes)) {
180
+ return 'text'
181
+ }
182
+
183
+ return 'binary'
184
+ }
185
+
186
+ /**
187
+ * Returns true when bytes start with a prefix.
188
+ * @param {Uint8Array} bytes Payload bytes.
189
+ * @param {number[]} prefix Prefix bytes.
190
+ * @returns {boolean}
191
+ */
192
+ static #hasPrefix(bytes, prefix) {
193
+ return prefix.every((value, index) => bytes[index] === value)
194
+ }
195
+
196
+ /**
197
+ * Decodes a short ASCII prefix.
198
+ * @param {Uint8Array} bytes Payload bytes.
199
+ * @returns {string}
200
+ */
201
+ static #asciiPrefix(bytes) {
202
+ return new TextDecoder('latin1').decode(bytes.slice(0, 8))
203
+ }
204
+
205
+ /**
206
+ * Decodes and trims a text probe.
207
+ * @param {Uint8Array} bytes Payload bytes.
208
+ * @returns {string}
209
+ */
210
+ static #trimmedText(bytes) {
211
+ return new TextDecoder('utf-8', { fatal: false })
212
+ .decode(bytes.slice(0, Math.min(bytes.byteLength, 256)))
213
+ .trim()
214
+ }
215
+
216
+ /**
217
+ * Returns true when a payload is printable enough to treat as text.
218
+ * @param {Uint8Array} bytes Payload bytes.
219
+ * @returns {boolean}
220
+ */
221
+ static #isLikelyText(bytes) {
222
+ let printable = 0
223
+ const length = Math.min(bytes.byteLength, 256)
224
+
225
+ for (let index = 0; index < length; index += 1) {
226
+ const value = bytes[index]
227
+ if (
228
+ value === 0x09 ||
229
+ value === 0x0a ||
230
+ value === 0x0d ||
231
+ (value >= 0x20 && value <= 0x7e)
232
+ ) {
233
+ printable += 1
234
+ }
235
+ }
236
+
237
+ return length > 0 && printable / length >= 0.9
238
+ }
239
+
240
+ /**
241
+ * Computes an FNV-1a 32-bit checksum.
242
+ * @param {Uint8Array} bytes Payload bytes.
243
+ * @returns {string}
244
+ */
245
+ static #fnv1a32(bytes) {
246
+ let hash = 0x811c9dc5
247
+
248
+ for (const value of bytes) {
249
+ hash ^= value
250
+ hash = Math.imul(hash, 0x01000193) >>> 0
251
+ }
252
+
253
+ return hash.toString(16).padStart(8, '0')
254
+ }
255
+ }
@@ -0,0 +1,240 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { NormalizedModelSchema } from './NormalizedModelSchema.mjs'
6
+
7
+ /**
8
+ * Normalizes integrated-library extraction into the public parser model.
9
+ */
10
+ export class IntLibModelParser {
11
+ /**
12
+ * Builds one integrated-library model.
13
+ * @param {string} fileName
14
+ * @param {{ version?: string, crossReferences?: object[], parameters?: Record<string, string>, parameterRecords?: object[], sources?: object[], streamNames?: string[], diagnostics?: Record<string, number> } | null} extraction
15
+ * @returns {{ schema: string, kind: 'integrated-library', fileType: 'IntLib', fileName: string, summary: Record<string, number | string>, diagnostics: { severity: 'info' | 'warning', message: string }[], integratedLibrary: object, bom: [] }}
16
+ */
17
+ static parse(fileName, extraction = null) {
18
+ const normalizedExtraction = extraction || {}
19
+ const sources = Array.isArray(normalizedExtraction.sources)
20
+ ? normalizedExtraction.sources
21
+ : []
22
+ const crossReferences = Array.isArray(
23
+ normalizedExtraction.crossReferences
24
+ )
25
+ ? normalizedExtraction.crossReferences
26
+ : []
27
+ const parameters = normalizedExtraction.parameters || {}
28
+ const streamNames = Array.isArray(normalizedExtraction.streamNames)
29
+ ? normalizedExtraction.streamNames
30
+ : []
31
+
32
+ return NormalizedModelSchema.attach({
33
+ kind: 'integrated-library',
34
+ fileType: 'IntLib',
35
+ fileName,
36
+ summary: {
37
+ title: IntLibModelParser.#stripExtension(fileName),
38
+ version: normalizedExtraction.version || '',
39
+ sourceCount: sources.length,
40
+ crossReferenceCount: crossReferences.length,
41
+ parameterCount: Object.keys(parameters).length
42
+ },
43
+ diagnostics: IntLibModelParser.#buildDiagnostics(
44
+ streamNames,
45
+ sources,
46
+ crossReferences,
47
+ parameters,
48
+ normalizedExtraction.diagnostics?.issues || []
49
+ ),
50
+ integratedLibrary: {
51
+ version: normalizedExtraction.version || '',
52
+ streamNames,
53
+ crossReferences,
54
+ parameters,
55
+ parameterRecords: normalizedExtraction.parameterRecords || [],
56
+ diagnostics: {
57
+ ...(normalizedExtraction.diagnostics || {}),
58
+ issues: normalizedExtraction.diagnostics?.issues || []
59
+ },
60
+ indexes: IntLibModelParser.#buildIndexes(
61
+ sources,
62
+ crossReferences
63
+ ),
64
+ sources
65
+ },
66
+ bom: []
67
+ })
68
+ }
69
+
70
+ /**
71
+ * Builds source and model lookup indexes for bundled library consumers.
72
+ * @param {object[]} sources Bundled source entries.
73
+ * @param {object[]} crossReferences Cross-reference rows.
74
+ * @returns {object}
75
+ */
76
+ static #buildIndexes(sources, crossReferences) {
77
+ return {
78
+ sourcesByFileName:
79
+ IntLibModelParser.#buildSourcesByFileName(sources),
80
+ sourcesByKind: IntLibModelParser.#buildSourcesByKind(sources),
81
+ modelsByComponent:
82
+ IntLibModelParser.#buildModelsByComponent(crossReferences),
83
+ symbolsByComponent: IntLibModelParser.#buildModelNamesByKind(
84
+ crossReferences,
85
+ 'SCH'
86
+ ),
87
+ footprintsByComponent: IntLibModelParser.#buildModelNamesByKind(
88
+ crossReferences,
89
+ 'PCB'
90
+ )
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Indexes source entries by file name.
96
+ * @param {object[]} sources Source entries.
97
+ * @returns {Record<string, object>}
98
+ */
99
+ static #buildSourcesByFileName(sources) {
100
+ const index = {}
101
+ for (const [sourceIndex, source] of [...sources]
102
+ .map((source, index) => ({ ...source, sourceIndex: index }))
103
+ .sort((left, right) =>
104
+ String(left.fileName || '').localeCompare(
105
+ String(right.fileName || '')
106
+ )
107
+ )
108
+ .entries()) {
109
+ index[source.fileName] = {
110
+ index: source.sourceIndex ?? sourceIndex,
111
+ path: source.path,
112
+ fileType: source.fileType,
113
+ libraryKind: source.libraryKind
114
+ }
115
+ }
116
+ return index
117
+ }
118
+
119
+ /**
120
+ * Indexes source file names by source kind.
121
+ * @param {object[]} sources Source entries.
122
+ * @returns {Record<string, string[]>}
123
+ */
124
+ static #buildSourcesByKind(sources) {
125
+ const index = {}
126
+ for (const source of [...sources].sort((left, right) =>
127
+ String(left.libraryKind || '').localeCompare(
128
+ String(right.libraryKind || '')
129
+ )
130
+ )) {
131
+ const kind = source.libraryKind || 'other'
132
+ index[kind] ||= []
133
+ index[kind].push(source.fileName)
134
+ }
135
+ return index
136
+ }
137
+
138
+ /**
139
+ * Indexes cross-reference model rows by component name.
140
+ * @param {object[]} crossReferences Cross-reference rows.
141
+ * @returns {Record<string, object[]>}
142
+ */
143
+ static #buildModelsByComponent(crossReferences) {
144
+ const index = {}
145
+ for (const row of crossReferences || []) {
146
+ if (!row.component || !row.model) continue
147
+ index[row.component] ||= []
148
+ index[row.component].push({
149
+ model: row.model,
150
+ kind: row.kind || ''
151
+ })
152
+ }
153
+ return index
154
+ }
155
+
156
+ /**
157
+ * Indexes cross-reference model names of one kind by component.
158
+ * @param {object[]} crossReferences Cross-reference rows.
159
+ * @param {string} kind Model kind.
160
+ * @returns {Record<string, string[]>}
161
+ */
162
+ static #buildModelNamesByKind(crossReferences, kind) {
163
+ const index = {}
164
+ const lookup = kind.toUpperCase()
165
+ for (const row of crossReferences || []) {
166
+ if (
167
+ !row.component ||
168
+ !row.model ||
169
+ String(row.kind || '').toUpperCase() !== lookup
170
+ ) {
171
+ continue
172
+ }
173
+ index[row.component] ||= []
174
+ index[row.component].push(row.model)
175
+ }
176
+ return index
177
+ }
178
+
179
+ /**
180
+ * Builds parser diagnostics for one integrated-library model.
181
+ * @param {string[]} streamNames
182
+ * @param {object[]} sources
183
+ * @param {object[]} crossReferences
184
+ * @param {Record<string, string>} parameters
185
+ * @param {object[]} issues Structured parser issues.
186
+ * @returns {{ severity: 'info' | 'warning', message: string }[]}
187
+ */
188
+ static #buildDiagnostics(
189
+ streamNames,
190
+ sources,
191
+ crossReferences,
192
+ parameters,
193
+ issues
194
+ ) {
195
+ return [
196
+ {
197
+ severity: 'info',
198
+ message:
199
+ 'Recovered ' +
200
+ streamNames.length +
201
+ ' integrated-library data streams.'
202
+ },
203
+ {
204
+ severity: 'info',
205
+ message:
206
+ 'Recovered ' +
207
+ sources.length +
208
+ ' bundled library source entries.'
209
+ },
210
+ {
211
+ severity: 'info',
212
+ message:
213
+ 'Recovered ' +
214
+ crossReferences.length +
215
+ ' integrated-library cross references.'
216
+ },
217
+ {
218
+ severity: 'info',
219
+ message:
220
+ 'Recovered ' +
221
+ Object.keys(parameters).length +
222
+ ' integrated-library parameters.'
223
+ },
224
+ ...(issues || []).map((issue) => ({
225
+ severity: issue.severity || 'warning',
226
+ code: issue.code,
227
+ message: issue.message
228
+ }))
229
+ ]
230
+ }
231
+
232
+ /**
233
+ * Returns a file name without its last extension.
234
+ * @param {string} fileName
235
+ * @returns {string}
236
+ */
237
+ static #stripExtension(fileName) {
238
+ return String(fileName || '').replace(/\.[^.]+$/u, '')
239
+ }
240
+ }