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
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2026 André Fiedler
3
+ //
4
+ // SPDX-License-Identifier: GPL-3.0-or-later
5
+
6
+ import {
7
+ modelIdentity,
8
+ printJson,
9
+ runReadOnlyScript,
10
+ wantsJson
11
+ } from './cli-utils.mjs'
12
+
13
+ const VIEWS = new Set(['summary', 'flat', 'hierarchy', 'parts', 'nets', 'all'])
14
+
15
+ /**
16
+ * Resolves the requested inspection view.
17
+ * @param {string[]} args CLI arguments.
18
+ * @returns {string}
19
+ */
20
+ function viewFromArgs(args) {
21
+ const inline = args.find((arg) => arg.startsWith('--view='))
22
+ if (inline) {
23
+ return inline.slice('--view='.length)
24
+ }
25
+
26
+ const index = args.indexOf('--view')
27
+ if (index >= 0) {
28
+ return args[index + 1] || ''
29
+ }
30
+
31
+ return 'summary'
32
+ }
33
+
34
+ /**
35
+ * Returns true when a requested view is supported.
36
+ * @param {string} view Requested view.
37
+ * @returns {boolean}
38
+ */
39
+ function isSupportedView(view) {
40
+ return VIEWS.has(view)
41
+ }
42
+
43
+ /**
44
+ * Builds one schematic inspection report.
45
+ * @param {object} model Parsed model.
46
+ * @param {string} view Requested view.
47
+ * @returns {object}
48
+ */
49
+ function buildReport(model, view) {
50
+ const base = {
51
+ ...modelIdentity(model),
52
+ view,
53
+ summary: buildSummary(model)
54
+ }
55
+
56
+ if (view === 'flat') {
57
+ return {
58
+ ...base,
59
+ records: flatRecords(model)
60
+ }
61
+ }
62
+
63
+ if (view === 'hierarchy') {
64
+ return {
65
+ ...base,
66
+ hierarchy: schematic(model).ownership?.hierarchy || []
67
+ }
68
+ }
69
+
70
+ if (view === 'parts') {
71
+ return {
72
+ ...base,
73
+ parts: partRows(model)
74
+ }
75
+ }
76
+
77
+ if (view === 'nets') {
78
+ return {
79
+ ...base,
80
+ nets: schematic(model).nets || []
81
+ }
82
+ }
83
+
84
+ if (view === 'all') {
85
+ return {
86
+ ...base,
87
+ flat: flatRecords(model),
88
+ hierarchy: schematic(model).ownership?.hierarchy || [],
89
+ parts: partRows(model),
90
+ nets: schematic(model).nets || []
91
+ }
92
+ }
93
+
94
+ return {
95
+ ...base,
96
+ recordTypes: schematic(model).recordTypes || [],
97
+ fieldCoverage: schematic(model).qa?.fieldCoverage || {
98
+ recordTypes: []
99
+ }
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Builds a compact schematic summary.
105
+ * @param {object} model Parsed model.
106
+ * @returns {object}
107
+ */
108
+ function buildSummary(model) {
109
+ const root = schematic(model)
110
+ const fieldCoverage = root.qa?.fieldCoverage?.summary || {}
111
+
112
+ return {
113
+ recordCount: (root.ownership?.records || []).length,
114
+ topLevelRecordCount: (root.ownership?.hierarchy || []).length,
115
+ componentCount: (root.components || []).length,
116
+ netCount: (root.nets || []).length,
117
+ diagnosticCount: (model.diagnostics || []).length,
118
+ fieldGapRecordTypeCount: fieldCoverage.recordTypeCount || 0,
119
+ unrecognizedFieldCount: fieldCoverage.unrecognizedFieldCount || 0
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Returns the schematic root or an empty object.
125
+ * @param {object} model Parsed model.
126
+ * @returns {object}
127
+ */
128
+ function schematic(model) {
129
+ return model.schematic || {}
130
+ }
131
+
132
+ /**
133
+ * Returns flat ownership records from the schematic sidecar.
134
+ * @param {object} model Parsed model.
135
+ * @returns {object[]}
136
+ */
137
+ function flatRecords(model) {
138
+ return schematic(model).ownership?.records || []
139
+ }
140
+
141
+ /**
142
+ * Builds compact part rows with recovered pin counts.
143
+ * @param {object} model Parsed model.
144
+ * @returns {object[]}
145
+ */
146
+ function partRows(model) {
147
+ const root = schematic(model)
148
+ const ownerIndexByDesignator = buildOwnerIndexByDesignator(root)
149
+
150
+ return (root.components || []).map((component) => ({
151
+ designator: component.designator || '',
152
+ libReference: component.libReference || '',
153
+ value: component.value || '',
154
+ uniqueId: component.uniqueId || '',
155
+ x: component.x,
156
+ y: component.y,
157
+ pinCount: pinCountForComponent(
158
+ root,
159
+ component.designator || '',
160
+ ownerIndexByDesignator
161
+ )
162
+ }))
163
+ }
164
+
165
+ /**
166
+ * Builds component owner-index lookups by visible designator.
167
+ * @param {object} root Schematic root.
168
+ * @returns {Map<string, string>}
169
+ */
170
+ function buildOwnerIndexByDesignator(root) {
171
+ const records = root.ownership?.records || []
172
+ const ownerIndexByDesignator = new Map()
173
+
174
+ for (const record of records) {
175
+ if (record.recordType !== '1' || record.indexInSheet === undefined) {
176
+ continue
177
+ }
178
+
179
+ const designator = findDesignatorForOwner(records, record.indexInSheet)
180
+ if (designator) {
181
+ ownerIndexByDesignator.set(designator, String(record.indexInSheet))
182
+ }
183
+ }
184
+
185
+ return ownerIndexByDesignator
186
+ }
187
+
188
+ /**
189
+ * Finds a visible designator text record for one component owner index.
190
+ * @param {object[]} records Ownership sidecar records.
191
+ * @param {number | string} ownerIndex Component owner index.
192
+ * @returns {string}
193
+ */
194
+ function findDesignatorForOwner(records, ownerIndex) {
195
+ const owner = String(ownerIndex)
196
+ const record = records.find(
197
+ (item) =>
198
+ String(item.ownerIndex || '') === owner &&
199
+ String(item.name || '').toLowerCase() === 'designator' &&
200
+ String(item.text || '').trim()
201
+ )
202
+
203
+ return String(record?.text || '').trim()
204
+ }
205
+
206
+ /**
207
+ * Counts pins belonging to one recovered component.
208
+ * @param {object} root Schematic root.
209
+ * @param {string} designator Component designator.
210
+ * @param {Map<string, string>} ownerIndexByDesignator Owner index lookup.
211
+ * @returns {number}
212
+ */
213
+ function pinCountForComponent(root, designator, ownerIndexByDesignator) {
214
+ const ownerIndex = ownerIndexByDesignator.get(designator) || ''
215
+
216
+ return (root.pins || []).filter((pin) => {
217
+ if (pin.componentDesignator === designator) {
218
+ return true
219
+ }
220
+
221
+ return ownerIndex && String(pin.ownerIndex || '') === ownerIndex
222
+ }).length
223
+ }
224
+
225
+ /**
226
+ * Prints a text report for the requested view.
227
+ * @param {object} report Inspection report.
228
+ * @returns {void}
229
+ */
230
+ function printTextReport(report) {
231
+ if (report.view === 'flat') {
232
+ printFlatText(report)
233
+ return
234
+ }
235
+
236
+ if (report.view === 'hierarchy') {
237
+ printHierarchyText(report)
238
+ return
239
+ }
240
+
241
+ if (report.view === 'parts') {
242
+ printPartsText(report)
243
+ return
244
+ }
245
+
246
+ if (report.view === 'nets') {
247
+ printNetsText(report)
248
+ return
249
+ }
250
+
251
+ if (report.view === 'all') {
252
+ printAllText(report)
253
+ return
254
+ }
255
+
256
+ printSummaryText(report)
257
+ }
258
+
259
+ /**
260
+ * Prints the summary view.
261
+ * @param {object} report Inspection report.
262
+ * @returns {void}
263
+ */
264
+ function printSummaryText(report) {
265
+ console.log(report.title)
266
+ console.log('Type: ' + report.fileType)
267
+ console.log('Kind: ' + report.kind)
268
+ console.log('Records: ' + report.summary.recordCount)
269
+ console.log('Components: ' + report.summary.componentCount)
270
+ console.log('Nets: ' + report.summary.netCount)
271
+ console.log('Diagnostics: ' + report.summary.diagnosticCount)
272
+ console.log('Unrecognized fields: ' + report.summary.unrecognizedFieldCount)
273
+ }
274
+
275
+ /**
276
+ * Prints all compact schematic views.
277
+ * @param {object} report Inspection report.
278
+ * @returns {void}
279
+ */
280
+ function printAllText(report) {
281
+ printSummaryText(report)
282
+ console.log('')
283
+ printFlatText({ ...report, records: report.flat || [] })
284
+ console.log('')
285
+ printHierarchyText(report)
286
+ console.log('')
287
+ printPartsText(report)
288
+ console.log('')
289
+ printNetsText(report)
290
+ }
291
+
292
+ /**
293
+ * Prints the flat record view.
294
+ * @param {object} report Inspection report.
295
+ * @returns {void}
296
+ */
297
+ function printFlatText(report) {
298
+ console.log(report.title + ' flat records')
299
+ for (const record of report.records || []) {
300
+ console.log(recordLine(record))
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Prints the hierarchy view.
306
+ * @param {object} report Inspection report.
307
+ * @returns {void}
308
+ */
309
+ function printHierarchyText(report) {
310
+ console.log(report.title + ' hierarchy')
311
+ for (const node of report.hierarchy || []) {
312
+ printHierarchyNode(node, 0)
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Prints one hierarchy node and its descendants.
318
+ * @param {object} node Hierarchy node.
319
+ * @param {number} depth Nesting depth.
320
+ * @returns {void}
321
+ */
322
+ function printHierarchyNode(node, depth) {
323
+ console.log(' '.repeat(depth) + recordLine(node))
324
+ for (const child of node.children || []) {
325
+ printHierarchyNode(child, depth + 1)
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Prints compact part rows.
331
+ * @param {object} report Inspection report.
332
+ * @returns {void}
333
+ */
334
+ function printPartsText(report) {
335
+ console.log(report.title + ' parts')
336
+ for (const part of report.parts || []) {
337
+ console.log(
338
+ [
339
+ part.designator || '(unnamed)',
340
+ part.libReference || '(no-library)',
341
+ 'pins=' + part.pinCount
342
+ ].join(' ')
343
+ )
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Prints compact net rows.
349
+ * @param {object} report Inspection report.
350
+ * @returns {void}
351
+ */
352
+ function printNetsText(report) {
353
+ console.log(report.title + ' nets')
354
+ for (const net of report.nets || []) {
355
+ console.log(
356
+ [
357
+ net.name || '(unnamed)',
358
+ 'segments=' + (net.segments || []).length,
359
+ 'pins=' + (net.pins || []).length,
360
+ 'labels=' + (net.labels || []).length
361
+ ].join(' ')
362
+ )
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Builds one readable record line.
368
+ * @param {object} record Ownership sidecar record.
369
+ * @returns {string}
370
+ */
371
+ function recordLine(record) {
372
+ const label = [record.name, record.text].filter(Boolean).join('=')
373
+
374
+ return ['#' + record.recordIndex, 'RECORD=' + record.recordType, label]
375
+ .filter(Boolean)
376
+ .join(' ')
377
+ }
378
+
379
+ await runReadOnlyScript({
380
+ scriptName: 'inspect-schematic',
381
+ summary: 'Inspect a parsed schematic and print a read-only view.',
382
+ helpLines: [
383
+ 'Use --view summary, flat, hierarchy, parts, nets, or all.',
384
+ 'Use --json for the full structured view.'
385
+ ],
386
+ run(model, args) {
387
+ const view = viewFromArgs(args)
388
+ if (!isSupportedView(view)) {
389
+ console.error(
390
+ 'Unsupported view "' +
391
+ view +
392
+ '". Use summary, flat, hierarchy, parts, nets, or all.'
393
+ )
394
+ process.exitCode = 1
395
+ return
396
+ }
397
+
398
+ const report = buildReport(model, view)
399
+ if (wantsJson(args)) {
400
+ printJson(report)
401
+ return
402
+ }
403
+
404
+ printTextReport(report)
405
+ }
406
+ })
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2026 André Fiedler
3
+ //
4
+ // SPDX-License-Identifier: GPL-3.0-or-later
5
+
6
+ import {
7
+ modelIdentity,
8
+ printCsv,
9
+ printJson,
10
+ runReadOnlyScript,
11
+ wantsJson
12
+ } from './cli-utils.mjs'
13
+ import { LibraryCatalogArtifactBuilder } from '../src/index.mjs'
14
+
15
+ /**
16
+ * Builds library catalog rows from parsed library models.
17
+ * @param {object} model Parsed model.
18
+ * @returns {object[]}
19
+ */
20
+ function catalogRows(model) {
21
+ const artifact = catalogArtifact(model)
22
+ if (artifact) {
23
+ return artifact.entries.map((entry) => ({
24
+ kind: entry.kind,
25
+ name: entry.name,
26
+ primitiveCount: entry.primitiveCount || '',
27
+ rawRecordCount: entry.rawRecordCount || '',
28
+ issueCount: entry.issueCodes.length,
29
+ outputSvgKey: entry.outputSvgKey || ''
30
+ }))
31
+ }
32
+
33
+ const footprintRows = (model.pcbLibrary?.footprints || []).map(
34
+ (footprint) => ({
35
+ kind: 'pcb-footprint',
36
+ name: footprint.name || footprint.sourceStorage || '',
37
+ primitiveCount:
38
+ (footprint.pads || []).length +
39
+ (footprint.tracks || []).length +
40
+ (footprint.arcs || []).length +
41
+ (footprint.vias || []).length +
42
+ (footprint.fills || []).length +
43
+ (footprint.regions || []).length,
44
+ rawRecordCount: (footprint.rawRecords || []).length
45
+ })
46
+ )
47
+ const sourceRows = (model.integratedLibrary?.sources || []).map(
48
+ (source) => ({
49
+ kind: source.fileType || 'source',
50
+ name: source.name || source.fileName || '',
51
+ primitiveCount: '',
52
+ rawRecordCount: '',
53
+ issueCount: '',
54
+ outputSvgKey: ''
55
+ })
56
+ )
57
+
58
+ return [...footprintRows, ...sourceRows]
59
+ }
60
+
61
+ /**
62
+ * Builds a static catalog artifact for parsed library roots.
63
+ * @param {object} model Parsed model.
64
+ * @returns {object | null}
65
+ */
66
+ function catalogArtifact(model) {
67
+ if (model.schematicLibrary) {
68
+ return LibraryCatalogArtifactBuilder.build({
69
+ schematicLibraries: [model]
70
+ })
71
+ }
72
+ if (model.pcbLibrary) {
73
+ return LibraryCatalogArtifactBuilder.build({
74
+ pcbLibraries: [model]
75
+ })
76
+ }
77
+
78
+ return null
79
+ }
80
+
81
+ await runReadOnlyScript({
82
+ scriptName: 'library-catalog',
83
+ summary: 'Catalog parsed library contents without modifying source files.',
84
+ helpLines: [
85
+ 'Default output is CSV. Use --json for structured rows.',
86
+ 'Use --html with SchLib/PcbLib inputs for a static catalog artifact.'
87
+ ],
88
+ run(model, args) {
89
+ const artifact = catalogArtifact(model)
90
+ if (args.includes('--html') && artifact) {
91
+ console.log(artifact.html)
92
+ return
93
+ }
94
+ const rows = catalogRows(model)
95
+ if (wantsJson(args)) {
96
+ printJson({
97
+ ...modelIdentity(model),
98
+ ...(artifact ? { catalog: artifact } : {}),
99
+ rows
100
+ })
101
+ return
102
+ }
103
+ printCsv(
104
+ [
105
+ 'kind',
106
+ 'name',
107
+ 'primitiveCount',
108
+ 'rawRecordCount',
109
+ 'issueCount',
110
+ 'outputSvgKey'
111
+ ],
112
+ rows
113
+ )
114
+ }
115
+ })
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2026 André Fiedler
3
+ //
4
+ // SPDX-License-Identifier: GPL-3.0-or-later
5
+
6
+ import {
7
+ modelIdentity,
8
+ printCsv,
9
+ printJson,
10
+ runReadOnlyScript,
11
+ wantsJson
12
+ } from './cli-utils.mjs'
13
+
14
+ /**
15
+ * Builds schematic and PCB net report rows.
16
+ * @param {object} model Parsed model.
17
+ * @returns {object[]}
18
+ */
19
+ function netRows(model) {
20
+ const schematicRows = (model.schematic?.nets || []).map((net) => ({
21
+ source: 'schematic',
22
+ name: net.name || '',
23
+ segmentCount: (net.segments || []).length,
24
+ pinCount: (net.pins || []).length,
25
+ primitiveCount: ''
26
+ }))
27
+ const pcbRows = (model.pcb?.nets || []).map((net) => ({
28
+ source: 'pcb',
29
+ name: net.name || '',
30
+ segmentCount: '',
31
+ pinCount: '',
32
+ primitiveCount:
33
+ model.pcb?.routeAnalysis?.nets?.[net.name]?.primitiveCount || ''
34
+ }))
35
+
36
+ return [...schematicRows, ...pcbRows].sort((left, right) =>
37
+ (left.source + ':' + left.name).localeCompare(
38
+ right.source + ':' + right.name
39
+ )
40
+ )
41
+ }
42
+
43
+ await runReadOnlyScript({
44
+ scriptName: 'net-report',
45
+ summary: 'Report recovered schematic and PCB net names.',
46
+ helpLines: ['Default output is CSV. Use --json for structured rows.'],
47
+ run(model, args) {
48
+ const rows = netRows(model)
49
+ if (wantsJson(args)) {
50
+ printJson({
51
+ ...modelIdentity(model),
52
+ rows
53
+ })
54
+ return
55
+ }
56
+ printCsv(
57
+ ['source', 'name', 'segmentCount', 'pinCount', 'primitiveCount'],
58
+ rows
59
+ )
60
+ }
61
+ })
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2026 André Fiedler
3
+ //
4
+ // SPDX-License-Identifier: GPL-3.0-or-later
5
+
6
+ import { LibraryQaReportBuilder } from '../src/index.mjs'
7
+
8
+ import {
9
+ modelIdentity,
10
+ printJson,
11
+ runReadOnlyScript,
12
+ wantsJson
13
+ } from './cli-utils.mjs'
14
+
15
+ /**
16
+ * Builds a read-only library QA report for one parsed model.
17
+ * @param {object} model Parsed model.
18
+ * @returns {object}
19
+ */
20
+ function validationReport(model) {
21
+ const report = LibraryQaReportBuilder.build({
22
+ pcbLibraries: model.pcbLibrary ? [model] : [],
23
+ schematicLibraries: model.schematicLibrary ? [model] : []
24
+ })
25
+
26
+ return {
27
+ ...modelIdentity(model),
28
+ report
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Prints a concise validation summary.
34
+ * @param {object} report Validation report.
35
+ * @returns {void}
36
+ */
37
+ function printTextReport(report) {
38
+ console.log(report.title)
39
+ console.log('Type: ' + report.fileType)
40
+ console.log('Issues: ' + report.report.summary.issueCount)
41
+ console.log(
42
+ 'Duplicate footprints: ' + report.report.summary.duplicateFootprintCount
43
+ )
44
+ console.log('Missing models: ' + report.report.summary.missingModelCount)
45
+ }
46
+
47
+ await runReadOnlyScript({
48
+ scriptName: 'validate-library',
49
+ summary: 'Run read-only library QA checks against a parsed library file.',
50
+ helpLines: ['Use --json for the full structured QA report.'],
51
+ run(model, args) {
52
+ const report = validationReport(model)
53
+ if (wantsJson(args)) {
54
+ printJson(report)
55
+ return
56
+ }
57
+ printTextReport(report)
58
+ }
59
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "altium-toolkit",
3
- "version": "1.1.3",
3
+ "version": "1.1.23",
4
4
  "description": "Altium document parsing and non-interactive rendering utilities",
5
5
  "keywords": [
6
6
  "altium",
@@ -31,6 +31,9 @@ rendering primitives.
31
31
  - Optional renderer CSS
32
32
  - Versioned normalized model schema identifiers and machine-readable schema
33
33
  contracts
34
+ - Source component client, source bundle exporter, generated `.SchLib` and
35
+ `.PcbLib` byte exporters, and batch export orchestration with progress,
36
+ checkpoint, append-skip, merged output, and retry-friendly client hooks
34
37
 
35
38
  ## Out Of Scope
36
39
 
@@ -40,6 +43,8 @@ rendering primitives.
40
43
  - Three.js runtime, OrbitControls, canvas mounting, and picking
41
44
  - STEP mesh loading and browser script injection
42
45
  - Model ZIP export UI and download orchestration
46
+ - Choosing a hosted component-source provider for applications; hosts inject
47
+ fetch and endpoint configuration explicitly
43
48
  - Server, deployment, and app metadata endpoints
44
49
  - Native document authoring, round-trip writing, GUI automation, and compiled
45
50
  multi-sheet project netlist generation