altium-toolkit 1.1.3 → 1.1.23

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 (125) hide show
  1. package/README.md +34 -5
  2. package/docs/api.md +171 -23
  3. package/docs/model-format.md +192 -20
  4. package/docs/schemas/altium_toolkit/embedded_assets_a1.schema.json +56 -0
  5. package/docs/schemas/altium_toolkit/fixture_coverage_matrix_a1.schema.json +89 -0
  6. package/docs/schemas/altium_toolkit/geometry_bounds_a1.schema.json +86 -0
  7. package/docs/schemas/altium_toolkit/library_catalog_a1.schema.json +65 -0
  8. package/docs/schemas/altium_toolkit/library_diff_a1.schema.json +54 -0
  9. package/docs/schemas/altium_toolkit/library_inspection_a1.schema.json +94 -0
  10. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +4 -0
  11. package/docs/schemas/altium_toolkit/native_stream_inventory_a1.schema.json +66 -0
  12. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +513 -3
  13. package/docs/schemas/altium_toolkit/parameter_record_inventory_a1.schema.json +84 -0
  14. package/docs/schemas/altium_toolkit/parser_diagnostics_a1.schema.json +63 -0
  15. package/docs/schemas/altium_toolkit/parser_value_verification_a1.schema.json +74 -0
  16. package/docs/schemas/altium_toolkit/pcb_class_report_a1.schema.json +79 -0
  17. package/docs/schemas/altium_toolkit/pcb_inspection_a1.schema.json +65 -0
  18. package/docs/schemas/altium_toolkit/pcb_net_membership_a1.schema.json +98 -0
  19. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +3 -0
  20. package/docs/schemas/altium_toolkit/project_hierarchy_a1.schema.json +79 -0
  21. package/docs/schemas/altium_toolkit/unsupported_features_a1.schema.json +212 -0
  22. package/docs/testing.md +7 -0
  23. package/examples/README.md +21 -0
  24. package/examples/cli-utils.mjs +148 -0
  25. package/examples/corpus-smoke.mjs +523 -0
  26. package/examples/extract-bom.mjs +47 -0
  27. package/examples/generate-pnp.mjs +59 -0
  28. package/examples/inspect-board.mjs +70 -0
  29. package/examples/inspect-schematic.mjs +406 -0
  30. package/examples/library-catalog.mjs +115 -0
  31. package/examples/net-report.mjs +61 -0
  32. package/examples/validate-library.mjs +59 -0
  33. package/package.json +1 -1
  34. package/spec/library-scope.md +5 -0
  35. package/src/core/BinaryReader.mjs +213 -2
  36. package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
  37. package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
  38. package/src/core/altium/AltiumParser.mjs +357 -16
  39. package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
  40. package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
  41. package/src/core/altium/AltiumUnits.mjs +205 -0
  42. package/src/core/altium/AsciiRecordParser.mjs +52 -11
  43. package/src/core/altium/EmbeddedAssetReportBuilder.mjs +383 -0
  44. package/src/core/altium/FixtureCoverageMatrixBuilder.mjs +304 -0
  45. package/src/core/altium/GeometryBoundsReportBuilder.mjs +935 -0
  46. package/src/core/altium/LibraryCatalogArtifactBuilder.mjs +296 -0
  47. package/src/core/altium/LibraryDiffReportBuilder.mjs +260 -0
  48. package/src/core/altium/LibraryInspectionReportBuilder.mjs +156 -0
  49. package/src/core/altium/LibraryQaReportBuilder.mjs +374 -1
  50. package/src/core/altium/NativeStreamInventoryBuilder.mjs +177 -0
  51. package/src/core/altium/NormalizedModelSchema.mjs +3 -31
  52. package/src/core/altium/ParameterCollection.mjs +431 -0
  53. package/src/core/altium/ParameterRecordInventoryBuilder.mjs +274 -0
  54. package/src/core/altium/ParserCompatibilityFuzzer.mjs +106 -2
  55. package/src/core/altium/ParserDiagnosticNormalizer.mjs +213 -0
  56. package/src/core/altium/ParserErrors.mjs +90 -0
  57. package/src/core/altium/ParserFieldCoverageReportBuilder.mjs +656 -0
  58. package/src/core/altium/ParserUtils.mjs +24 -0
  59. package/src/core/altium/ParserValueVerificationReportBuilder.mjs +323 -0
  60. package/src/core/altium/PcbClassReportBuilder.mjs +366 -0
  61. package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
  62. package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
  63. package/src/core/altium/PcbInspectionReportBuilder.mjs +313 -0
  64. package/src/core/altium/PcbLayerGroups.mjs +308 -0
  65. package/src/core/altium/PcbLayerStackCustomDataParser.mjs +183 -0
  66. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +473 -4
  67. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +83 -15
  68. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +74 -4
  69. package/src/core/altium/PcbLibModelParser.mjs +20 -4
  70. package/src/core/altium/PcbLibStreamExtractor.mjs +49 -6
  71. package/src/core/altium/PcbModelParser.mjs +223 -4
  72. package/src/core/altium/PcbNetMembershipReportBuilder.mjs +270 -0
  73. package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
  74. package/src/core/altium/PcbStreamExtractor.mjs +130 -6
  75. package/src/core/altium/PcbTrackPrimitiveParser.mjs +66 -2
  76. package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
  77. package/src/core/altium/ProjectHierarchyReportBuilder.mjs +660 -0
  78. package/src/core/altium/ProjectNetlistExporter.mjs +2 -0
  79. package/src/core/altium/RawDataPreservationReportBuilder.mjs +348 -0
  80. package/src/core/altium/SchLibModelParser.mjs +840 -0
  81. package/src/core/altium/SchLibStreamExtractor.mjs +586 -0
  82. package/src/core/altium/SchematicBusEntryParser.mjs +3 -2
  83. package/src/core/altium/SchematicCodeSymbolParser.mjs +663 -0
  84. package/src/core/altium/SchematicConnectivityQaBuilder.mjs +177 -2
  85. package/src/core/altium/SchematicDirectiveParser.mjs +5 -17
  86. package/src/core/altium/SchematicDisplayModeCatalogParser.mjs +10 -1
  87. package/src/core/altium/SchematicFieldCoverageReportBuilder.mjs +549 -0
  88. package/src/core/altium/SchematicHarnessParser.mjs +9 -3
  89. package/src/core/altium/SchematicHyperlinkParser.mjs +122 -0
  90. package/src/core/altium/SchematicNetlistBuilder.mjs +271 -8
  91. package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
  92. package/src/core/altium/SchematicOwnershipGraphParser.mjs +102 -3
  93. package/src/core/altium/SchematicPinParser.mjs +99 -65
  94. package/src/core/altium/SchematicPrimitiveParser.mjs +125 -22
  95. package/src/core/altium/SchematicQaReportBuilder.mjs +2 -0
  96. package/src/core/altium/SchematicRecordStreamParser.mjs +183 -0
  97. package/src/core/altium/SchematicRecordTypeRegistry.mjs +6 -1
  98. package/src/core/altium/SchematicSheetParser.mjs +8 -2
  99. package/src/core/altium/SchematicStreamExtractor.mjs +107 -21
  100. package/src/core/altium/SchematicTextOrientationResolver.mjs +76 -0
  101. package/src/core/altium/SchematicTextParser.mjs +28 -12
  102. package/src/core/altium/SchematicTextRunParser.mjs +81 -0
  103. package/src/core/altium/SchematicThumbnailParser.mjs +425 -0
  104. package/src/core/altium/SourceBundleExporter.mjs +156 -0
  105. package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
  106. package/src/core/altium/SourceComponentClient.mjs +239 -0
  107. package/src/core/altium/UnsupportedFeatureReportBuilder.mjs +380 -0
  108. package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
  109. package/src/parser.mjs +43 -1
  110. package/src/renderers.mjs +1 -0
  111. package/src/styles/altium-renderers.css +6 -6
  112. package/src/ui/PcbArcUtils.mjs +19 -2
  113. package/src/ui/PcbScene3dBuilder.mjs +202 -20
  114. package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
  115. package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
  116. package/src/ui/SchematicColorResolver.mjs +185 -0
  117. package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
  118. package/src/ui/SchematicLineColorResolver.mjs +88 -0
  119. package/src/ui/SchematicNoteRenderer.mjs +5 -1
  120. package/src/ui/SchematicOwnerPinLabelLayout.mjs +269 -8
  121. package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
  122. package/src/ui/SchematicPinSvgRenderer.mjs +229 -62
  123. package/src/ui/SchematicShapeRenderer.mjs +86 -17
  124. package/src/ui/SchematicSvgRenderer.mjs +980 -58
  125. package/src/ui/SchematicTypography.mjs +4 -3
@@ -2,6 +2,145 @@
2
2
  //
3
3
  // SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
+ /**
6
+ * Reads sequential little-endian primitive values from a BinaryReader.
7
+ */
8
+ export class BinaryReaderCursor {
9
+ #offset
10
+
11
+ #reader
12
+
13
+ /**
14
+ * @param {BinaryReader} reader
15
+ * @param {number} [offset]
16
+ */
17
+ constructor(reader, offset = 0) {
18
+ const normalizedOffset = Number(offset)
19
+ if (
20
+ !Number.isInteger(normalizedOffset) ||
21
+ normalizedOffset < 0 ||
22
+ normalizedOffset > reader.byteLength
23
+ ) {
24
+ throw new RangeError(
25
+ 'BinaryReader cursor offset is out of bounds: ' +
26
+ normalizedOffset
27
+ )
28
+ }
29
+
30
+ this.#reader = reader
31
+ this.#offset = normalizedOffset
32
+ }
33
+
34
+ /**
35
+ * Returns the current cursor offset.
36
+ * @returns {number}
37
+ */
38
+ get offset() {
39
+ return this.#offset
40
+ }
41
+
42
+ /**
43
+ * Reads one unsigned byte and advances the cursor.
44
+ * @returns {number}
45
+ */
46
+ readUint8() {
47
+ return this.#readNumber(1, (offset) => this.#reader.readUint8(offset))
48
+ }
49
+
50
+ /**
51
+ * Reads one signed 16-bit integer and advances the cursor.
52
+ * @returns {number}
53
+ */
54
+ readInt16() {
55
+ return this.#readNumber(2, (offset) => this.#reader.readInt16(offset))
56
+ }
57
+
58
+ /**
59
+ * Reads one unsigned 16-bit integer and advances the cursor.
60
+ * @returns {number}
61
+ */
62
+ readUint16() {
63
+ return this.#readNumber(2, (offset) => this.#reader.readUint16(offset))
64
+ }
65
+
66
+ /**
67
+ * Reads one unsigned 32-bit integer and advances the cursor.
68
+ * @returns {number}
69
+ */
70
+ readUint32() {
71
+ return this.#readNumber(4, (offset) => this.#reader.readUint32(offset))
72
+ }
73
+
74
+ /**
75
+ * Reads one signed 32-bit integer and advances the cursor.
76
+ * @returns {number}
77
+ */
78
+ readInt32() {
79
+ return this.#readNumber(4, (offset) => this.#reader.readInt32(offset))
80
+ }
81
+
82
+ /**
83
+ * Reads one unsigned 64-bit integer and advances the cursor.
84
+ * @returns {number}
85
+ */
86
+ readUint64() {
87
+ return this.#readNumber(8, (offset) => this.#reader.readUint64(offset))
88
+ }
89
+
90
+ /**
91
+ * Reads one 64-bit floating-point value and advances the cursor.
92
+ * @returns {number}
93
+ */
94
+ readFloat64() {
95
+ return this.#readNumber(8, (offset) => this.#reader.readFloat64(offset))
96
+ }
97
+
98
+ /**
99
+ * Reads one byte slice and advances the cursor.
100
+ * @param {number} length
101
+ * @returns {Uint8Array}
102
+ */
103
+ readBytes(length) {
104
+ const bytes = this.#reader.readBytes(this.#offset, length)
105
+ this.#offset += Number(length)
106
+ return bytes
107
+ }
108
+
109
+ /**
110
+ * Reads one length-prefixed string and advances the cursor.
111
+ * @param {string} [encoding]
112
+ * @returns {string}
113
+ */
114
+ readPascalString(encoding = 'utf-8') {
115
+ const length = this.#reader.readUint8(this.#offset)
116
+ const value = this.#reader.readPascalString(this.#offset, encoding)
117
+ this.#offset += 1 + length
118
+ return value
119
+ }
120
+
121
+ /**
122
+ * Reads one length-prefixed block and advances the cursor.
123
+ * @returns {{ byteLength: number, flags: number, dataOffset: number, nextOffset: number, bytes: Uint8Array }}
124
+ */
125
+ readSizedBlock() {
126
+ const block = this.#reader.readSizedBlock(this.#offset)
127
+ this.#offset = block.nextOffset
128
+ return block
129
+ }
130
+
131
+ /**
132
+ * Reads one fixed-width numeric value and advances the cursor.
133
+ * @param {number} size
134
+ * @param {(offset: number) => number} reader
135
+ * @returns {number}
136
+ */
137
+ #readNumber(size, reader) {
138
+ const value = reader(this.#offset)
139
+ this.#offset += size
140
+ return value
141
+ }
142
+ }
143
+
5
144
  /**
6
145
  * Reads little-endian primitive values from an ArrayBuffer with bounds checks.
7
146
  */
@@ -39,6 +178,16 @@ export class BinaryReader {
39
178
  return this.#dataView.getUint8(offset)
40
179
  }
41
180
 
181
+ /**
182
+ * Reads one signed 16-bit integer.
183
+ * @param {number} offset
184
+ * @returns {number}
185
+ */
186
+ readInt16(offset) {
187
+ this.#assertReadable(offset, 2)
188
+ return this.#dataView.getInt16(offset, true)
189
+ }
190
+
42
191
  /**
43
192
  * Reads one unsigned 16-bit integer.
44
193
  * @param {number} offset
@@ -69,6 +218,16 @@ export class BinaryReader {
69
218
  return this.#dataView.getInt32(offset, true)
70
219
  }
71
220
 
221
+ /**
222
+ * Reads one 64-bit floating-point value.
223
+ * @param {number} offset
224
+ * @returns {number}
225
+ */
226
+ readFloat64(offset) {
227
+ this.#assertReadable(offset, 8)
228
+ return this.#dataView.getFloat64(offset, true)
229
+ }
230
+
72
231
  /**
73
232
  * Reads one unsigned 64-bit integer as a JavaScript number.
74
233
  * @param {number} offset
@@ -97,8 +256,58 @@ export class BinaryReader {
97
256
  * @returns {Uint8Array}
98
257
  */
99
258
  readBytes(offset, length) {
100
- this.#assertReadable(offset, length)
101
- return new Uint8Array(this.#arrayBuffer.slice(offset, offset + length))
259
+ const normalizedOffset = Number(offset)
260
+ const normalizedLength = Number(length)
261
+ this.#assertReadable(normalizedOffset, normalizedLength)
262
+ return new Uint8Array(
263
+ this.#arrayBuffer.slice(
264
+ normalizedOffset,
265
+ normalizedOffset + normalizedLength
266
+ )
267
+ )
268
+ }
269
+
270
+ /**
271
+ * Reads one one-byte length-prefixed string.
272
+ * @param {number} offset
273
+ * @param {string} [encoding]
274
+ * @returns {string}
275
+ */
276
+ readPascalString(offset, encoding = 'utf-8') {
277
+ const length = this.readUint8(offset)
278
+ const bytes = this.readBytes(offset + 1, length)
279
+ return new TextDecoder(encoding).decode(bytes)
280
+ }
281
+
282
+ /**
283
+ * Reads one 32-bit length-prefixed byte block.
284
+ * @param {number} offset
285
+ * @returns {{ byteLength: number, flags: number, dataOffset: number, nextOffset: number, bytes: Uint8Array }}
286
+ */
287
+ readSizedBlock(offset) {
288
+ const normalizedOffset = Number(offset)
289
+ const rawLength = this.readUint32(normalizedOffset)
290
+ const byteLength = rawLength & 0x00ffffff
291
+ const flags = rawLength >>> 24
292
+ const dataOffset = normalizedOffset + 4
293
+ const bytes = this.readBytes(dataOffset, byteLength)
294
+
295
+ return {
296
+ byteLength,
297
+ flags,
298
+ dataOffset,
299
+ nextOffset: dataOffset + byteLength,
300
+ bytes
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Creates a sequential cursor over this reader.
306
+ * @param {number} [offset]
307
+ * @returns {BinaryReaderCursor}
308
+ */
309
+ cursor(offset = 0) {
310
+ return new BinaryReaderCursor(this, offset)
102
311
  }
103
312
 
104
313
  /**
@@ -112,7 +321,9 @@ export class BinaryReader {
112
321
 
113
322
  if (
114
323
  !Number.isInteger(normalizedOffset) ||
324
+ !Number.isInteger(normalizedSize) ||
115
325
  normalizedOffset < 0 ||
326
+ normalizedSize < 0 ||
116
327
  normalizedOffset + normalizedSize > this.#byteLength
117
328
  ) {
118
329
  throw new RangeError(
@@ -0,0 +1,206 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { AltiumPcbLibExporter } from './AltiumPcbLibExporter.mjs'
6
+ import { AltiumSchLibExporter } from './AltiumSchLibExporter.mjs'
7
+ import { SourceBundleExporter } from './SourceBundleExporter.mjs'
8
+
9
+ /**
10
+ * Orchestrates source lookup and local library export.
11
+ */
12
+ export class AltiumLibraryBatchExporter {
13
+ #client
14
+
15
+ /**
16
+ * @param {{ client?: { fetchComponentBundle?: Function, searchComponents?: Function } }} [options] Batch options.
17
+ */
18
+ constructor(options = {}) {
19
+ this.#client = options.client || null
20
+ }
21
+
22
+ /**
23
+ * Searches components and exports the matching ids.
24
+ * @param {string} query Search query.
25
+ * @param {object} [options] Export options.
26
+ * @returns {Promise<object>}
27
+ */
28
+ async searchAndExport(query, options = {}) {
29
+ const rows = await this.#requireClient().searchComponents(
30
+ query,
31
+ options
32
+ )
33
+ return this.exportIds(
34
+ rows.map((row) => row.id).filter(Boolean),
35
+ options
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Exports one list of provider ids.
41
+ * @param {string[]} ids Component ids.
42
+ * @param {{ appendManifest?: { completedIds?: string[] }, includeSourceBundle?: boolean, includeSchLib?: boolean, includePcbLib?: boolean, merged?: boolean, continueOnError?: boolean, onProgress?: Function }} [options] Export options.
43
+ * @returns {Promise<{ entries: object[], bundles: object[], diagnostics: object[], checkpoint: { completedIds: string[] } }>}
44
+ */
45
+ async exportIds(ids, options = {}) {
46
+ const completedIds = [
47
+ ...new Set(options.appendManifest?.completedIds || [])
48
+ ]
49
+ const completedSet = new Set(completedIds)
50
+ const entries = []
51
+ const bundles = []
52
+ const diagnostics = []
53
+
54
+ for (const id of ids
55
+ .map((value) => String(value || ''))
56
+ .filter(Boolean)) {
57
+ if (completedSet.has(id)) {
58
+ AltiumLibraryBatchExporter.#emitProgress(options, {
59
+ id,
60
+ status: 'skipped'
61
+ })
62
+ continue
63
+ }
64
+
65
+ try {
66
+ const bundle =
67
+ await this.#requireClient().fetchComponentBundle(id)
68
+ bundles.push(bundle)
69
+ entries.push(
70
+ ...AltiumLibraryBatchExporter.#buildPerComponentEntries(
71
+ id,
72
+ bundle,
73
+ options
74
+ )
75
+ )
76
+ completedSet.add(id)
77
+ completedIds.push(id)
78
+ AltiumLibraryBatchExporter.#emitProgress(options, {
79
+ id,
80
+ status: 'exported'
81
+ })
82
+ } catch (error) {
83
+ diagnostics.push({
84
+ id,
85
+ severity: 'error',
86
+ message: String(error?.message || error)
87
+ })
88
+ AltiumLibraryBatchExporter.#emitProgress(options, {
89
+ id,
90
+ status: 'failed'
91
+ })
92
+ if (!options.continueOnError) {
93
+ throw error
94
+ }
95
+ }
96
+ }
97
+
98
+ if (options.merged && bundles.length) {
99
+ entries.push(
100
+ ...AltiumLibraryBatchExporter.#buildMergedEntries(bundles)
101
+ )
102
+ }
103
+
104
+ return {
105
+ entries,
106
+ bundles,
107
+ diagnostics,
108
+ checkpoint: { completedIds }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Builds per-component export entries.
114
+ * @param {string} id Component id.
115
+ * @param {object} bundle Normalized bundle.
116
+ * @param {object} options Export options.
117
+ * @returns {object[]}
118
+ */
119
+ static #buildPerComponentEntries(id, bundle, options) {
120
+ const prefix = AltiumLibraryBatchExporter.#safePathSegment(id) + '/'
121
+ const entries = []
122
+
123
+ if (options.includeSourceBundle) {
124
+ entries.push(
125
+ ...SourceBundleExporter.export(bundle).entries.map((entry) => ({
126
+ ...entry,
127
+ path: prefix + entry.path
128
+ }))
129
+ )
130
+ }
131
+
132
+ if (options.includeSchLib) {
133
+ entries.push({
134
+ path: prefix + 'library.SchLib',
135
+ bytes: AltiumSchLibExporter.export([bundle]),
136
+ contentType: 'application/octet-stream'
137
+ })
138
+ }
139
+
140
+ if (options.includePcbLib) {
141
+ entries.push({
142
+ path: prefix + 'library.PcbLib',
143
+ bytes: AltiumPcbLibExporter.export([bundle]),
144
+ contentType: 'application/octet-stream'
145
+ })
146
+ }
147
+
148
+ return entries
149
+ }
150
+
151
+ /**
152
+ * Builds merged library output entries.
153
+ * @param {object[]} bundles Normalized bundles.
154
+ * @returns {object[]}
155
+ */
156
+ static #buildMergedEntries(bundles) {
157
+ return [
158
+ {
159
+ path: 'merged/library.SchLib',
160
+ bytes: AltiumSchLibExporter.export(bundles),
161
+ contentType: 'application/octet-stream'
162
+ },
163
+ {
164
+ path: 'merged/library.PcbLib',
165
+ bytes: AltiumPcbLibExporter.export(bundles),
166
+ contentType: 'application/octet-stream'
167
+ }
168
+ ]
169
+ }
170
+
171
+ /**
172
+ * Emits progress.
173
+ * @param {object} options Export options.
174
+ * @param {object} event Progress event.
175
+ * @returns {void}
176
+ */
177
+ static #emitProgress(options, event) {
178
+ if (typeof options.onProgress === 'function') {
179
+ options.onProgress(event)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Sanitizes one path segment.
185
+ * @param {string} value Raw value.
186
+ * @returns {string}
187
+ */
188
+ static #safePathSegment(value) {
189
+ return String(value || 'component').replace(
190
+ /[\\/:\u0000-\u001f]/gu,
191
+ '_'
192
+ )
193
+ }
194
+
195
+ /**
196
+ * Returns the configured client or throws.
197
+ * @returns {{ fetchComponentBundle?: Function, searchComponents?: Function }}
198
+ */
199
+ #requireClient() {
200
+ if (!this.#client) {
201
+ throw new Error('AltiumLibraryBatchExporter client is required.')
202
+ }
203
+
204
+ return this.#client
205
+ }
206
+ }