@grayhaven/nerve-exporters 0.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/dist/index.js ADDED
@@ -0,0 +1,1131 @@
1
+ // src/csv.ts
2
+ import { isPinEndpoint } from "@grayhaven/nerve";
3
+ var endpointCells = (e) => isPinEndpoint(e) ? [e.connector, e.pin] : [e.splice, ""];
4
+ var escapeCell = (cell) => {
5
+ if (cell === void 0) return "";
6
+ const s = String(cell);
7
+ return /[",\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
8
+ };
9
+ var toCsv = (rows) => rows.map((row) => row.map(escapeCell).join(",")).join("\n") + "\n";
10
+ var tableCsv = (table) => toCsv([table.headers, ...table.rows]);
11
+ var bomTable = (hir) => ({
12
+ headers: [
13
+ "Item number",
14
+ "Quantity",
15
+ "Unit of measure",
16
+ "Internal part number",
17
+ "Manufacturer",
18
+ "Manufacturer part number",
19
+ "Description",
20
+ "Category",
21
+ "Used by",
22
+ "Approved alternates",
23
+ "Notes"
24
+ ],
25
+ rows: hir.bom.map((item, i) => [
26
+ i + 1,
27
+ item.quantity,
28
+ item.unitOfMeasure,
29
+ item.internalPartId,
30
+ item.manufacturer,
31
+ item.mpn,
32
+ item.description,
33
+ item.category,
34
+ item.usedBy.join("; "),
35
+ "",
36
+ item.notes
37
+ ])
38
+ });
39
+ var bomCsv = (hir) => tableCsv(bomTable(hir));
40
+ var cutListTable = (hir, options = {}) => ({
41
+ headers: [
42
+ "Wire ID",
43
+ "Signal",
44
+ "Gauge",
45
+ "Color",
46
+ "Stripe",
47
+ "Cut length",
48
+ "Finished length",
49
+ "Tolerance",
50
+ "From connector",
51
+ "From pin",
52
+ "To connector",
53
+ "To pin",
54
+ "Terminal A",
55
+ "Terminal B",
56
+ "Branch",
57
+ "Notes"
58
+ ],
59
+ rows: hir.wires.map((w) => [
60
+ w.id,
61
+ w.signal,
62
+ w.gauge,
63
+ w.color,
64
+ w.stripe,
65
+ w.length,
66
+ w.length,
67
+ w.lengthTolerance ?? options.defaultWireTolerance,
68
+ ...endpointCells(w.from),
69
+ ...endpointCells(w.to),
70
+ "",
71
+ // terminal assignment lands with process data (PRD §28)
72
+ "",
73
+ w.branch ?? w.cable,
74
+ w.notes
75
+ ])
76
+ });
77
+ var cutListCsv = (hir, options = {}) => tableCsv(cutListTable(hir, options));
78
+ var labelScheduleTable = (hir) => ({
79
+ headers: [
80
+ "Label ID",
81
+ "Text",
82
+ "Quantity",
83
+ "Material",
84
+ "Printer profile",
85
+ "Target object",
86
+ "Placement offset",
87
+ "Orientation",
88
+ "Notes"
89
+ ],
90
+ rows: hir.labels.map((l) => [
91
+ l.id,
92
+ l.text,
93
+ l.quantity ?? 1,
94
+ l.material,
95
+ "",
96
+ l.attachTo,
97
+ l.offsetFrom !== void 0 && l.distance !== void 0 ? `${l.distance} from ${l.offsetFrom}` : l.distance,
98
+ "",
99
+ ""
100
+ ])
101
+ });
102
+ var labelScheduleCsv = (hir) => tableCsv(labelScheduleTable(hir));
103
+ var testPlanTable = (plan) => ({
104
+ headers: [
105
+ "Test ID",
106
+ "Type",
107
+ "From connector",
108
+ "From pin",
109
+ "To connector",
110
+ "To pin",
111
+ "Expected",
112
+ "Net",
113
+ "Wire"
114
+ ],
115
+ rows: plan.tests.map((t) => [
116
+ t.id,
117
+ t.type,
118
+ t.from.connector,
119
+ t.from.pin,
120
+ t.to.connector,
121
+ t.to.pin,
122
+ t.expected,
123
+ t.net,
124
+ t.wire
125
+ ])
126
+ });
127
+ var testPlanCsv = (plan) => tableCsv(testPlanTable(plan));
128
+
129
+ // src/test-plan.ts
130
+ import { isPinEndpoint as isPinEndpoint2 } from "@grayhaven/nerve";
131
+ var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
132
+ var endpointKey = (e) => isPinEndpoint2(e) ? `pin:${e.connector}:${e.pin}` : `splice:${e.splice}`;
133
+ var computeNets = (hir) => {
134
+ const parent = /* @__PURE__ */ new Map();
135
+ const find = (k) => {
136
+ let root = parent.get(k) ?? k;
137
+ if (root !== k) {
138
+ root = find(root);
139
+ parent.set(k, root);
140
+ }
141
+ return root;
142
+ };
143
+ const union = (a, b) => {
144
+ const ra = find(a);
145
+ const rb = find(b);
146
+ if (ra !== rb) parent.set(ra < rb ? rb : ra, ra < rb ? ra : rb);
147
+ };
148
+ for (const w of hir.wires) {
149
+ union(endpointKey(w.from), endpointKey(w.to));
150
+ }
151
+ const signalsByRoot = /* @__PURE__ */ new Map();
152
+ for (const w of hir.wires) {
153
+ if (w.signal === void 0) continue;
154
+ const root = find(endpointKey(w.from));
155
+ const list = signalsByRoot.get(root) ?? [];
156
+ list.push(w.signal);
157
+ signalsByRoot.set(root, list);
158
+ }
159
+ const names = /* @__PURE__ */ new Map();
160
+ for (const w of hir.wires) {
161
+ for (const e of [w.from, w.to]) {
162
+ const key = endpointKey(e);
163
+ const root = find(key);
164
+ const signals = signalsByRoot.get(root);
165
+ names.set(key, signals !== void 0 ? [...signals].sort(cmp)[0] : root);
166
+ }
167
+ }
168
+ return names;
169
+ };
170
+ var generateTestPlan = (hir) => {
171
+ const netOf = computeNets(hir);
172
+ const tests = [];
173
+ let n = 0;
174
+ const nextId = () => `T-${String(++n).padStart(3, "0")}`;
175
+ for (const w of hir.wires) {
176
+ if (!isPinEndpoint2(w.from) || !isPinEndpoint2(w.to)) continue;
177
+ const net = netOf.get(endpointKey(w.from));
178
+ tests.push({
179
+ id: nextId(),
180
+ type: "continuity",
181
+ from: { connector: w.from.connector, pin: w.from.pin },
182
+ to: { connector: w.to.connector, pin: w.to.pin },
183
+ expected: "closed",
184
+ ...net !== void 0 ? { net } : {},
185
+ wire: w.id
186
+ });
187
+ }
188
+ for (const s of hir.splices) {
189
+ const pins = [];
190
+ for (const wireId of s.wires) {
191
+ const w = hir.wires.find((x) => x.id === wireId);
192
+ if (w === void 0) continue;
193
+ for (const e of [w.from, w.to]) {
194
+ if (isPinEndpoint2(e)) pins.push({ connector: e.connector, pin: e.pin });
195
+ }
196
+ }
197
+ pins.sort((a, b) => cmp(a.connector, b.connector) || cmp(a.pin, b.pin));
198
+ const hub = pins[0];
199
+ if (hub === void 0) continue;
200
+ const net = netOf.get(`splice:${s.id}`);
201
+ for (const p of pins.slice(1)) {
202
+ tests.push({
203
+ id: nextId(),
204
+ type: "splice",
205
+ from: hub,
206
+ to: p,
207
+ expected: "closed",
208
+ ...net !== void 0 ? { net } : {},
209
+ splice: s.id
210
+ });
211
+ }
212
+ }
213
+ const netToPin = /* @__PURE__ */ new Map();
214
+ for (const w of hir.wires) {
215
+ for (const end of [w.from, w.to]) {
216
+ if (!isPinEndpoint2(end)) continue;
217
+ const net = netOf.get(endpointKey(end)) ?? `wire:${w.id}`;
218
+ const pins = netToPin.get(end.connector) ?? /* @__PURE__ */ new Map();
219
+ if (!pins.has(net)) pins.set(net, end.pin);
220
+ netToPin.set(end.connector, pins);
221
+ }
222
+ }
223
+ for (const connector of [...netToPin.keys()].sort(cmp)) {
224
+ const pins = netToPin.get(connector);
225
+ const nets = [...pins.keys()].sort(cmp);
226
+ for (let i = 0; i < nets.length; i++) {
227
+ for (let j = i + 1; j < nets.length; j++) {
228
+ const netA = nets[i];
229
+ const netB = nets[j];
230
+ tests.push({
231
+ id: nextId(),
232
+ type: "no-short",
233
+ from: { connector, pin: pins.get(netA) },
234
+ to: { connector, pin: pins.get(netB) },
235
+ expected: "open",
236
+ net: `${netA} <-> ${netB}`
237
+ });
238
+ }
239
+ }
240
+ }
241
+ return {
242
+ harness: { id: hir.harness.id, revision: hir.harness.revision },
243
+ tests
244
+ };
245
+ };
246
+ var testPlanJson = (hir) => JSON.stringify(generateTestPlan(hir), null, 2) + "\n";
247
+ var coverage = (hir, plan) => {
248
+ const netOf = computeNets(hir);
249
+ const nets = new Set(
250
+ hir.wires.map((w) => netOf.get(endpointKey(w.from)) ?? `wire:${w.id}`)
251
+ );
252
+ const covered = new Set(
253
+ plan.tests.filter((t) => t.expected === "closed").map((t) => t.net ?? (t.type === "continuity" ? `wire:${t.wire}` : ""))
254
+ );
255
+ return {
256
+ nets: nets.size,
257
+ covered: [...nets].filter((n) => covered.has(n)).length
258
+ };
259
+ };
260
+
261
+ // src/drawing.ts
262
+ var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
263
+ var renderSvg = (drawing) => {
264
+ const parts = [
265
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${drawing.width}" height="${drawing.height}" viewBox="0 0 ${drawing.width} ${drawing.height}" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="12">`
266
+ ];
267
+ if (drawing.background !== void 0) {
268
+ parts.push(
269
+ `<rect width="${drawing.width}" height="${drawing.height}" fill="${esc(drawing.background)}"/>`
270
+ );
271
+ }
272
+ for (const item of drawing.items) {
273
+ switch (item.kind) {
274
+ case "rect":
275
+ parts.push(
276
+ `<rect x="${item.x}" y="${item.y}" width="${item.w}" height="${item.h}"${item.rx !== void 0 ? ` rx="${item.rx}"` : ""} fill="${esc(item.fill ?? "none")}"${item.stroke !== void 0 ? ` stroke="${esc(item.stroke)}" stroke-width="${item.strokeWidth ?? 1}"` : ""}/>`
277
+ );
278
+ break;
279
+ case "line":
280
+ parts.push(
281
+ `<line x1="${item.x1}" y1="${item.y1}" x2="${item.x2}" y2="${item.y2}" stroke="${esc(item.stroke)}" stroke-width="${item.strokeWidth ?? 1}"${item.dash !== void 0 ? ` stroke-dasharray="${item.dash.join(" ")}"` : ""}/>`
282
+ );
283
+ break;
284
+ case "path":
285
+ parts.push(
286
+ `<path d="${esc(item.d)}" fill="none" stroke="${esc(item.stroke)}" stroke-width="${item.strokeWidth ?? 1}"${item.dash !== void 0 ? ` stroke-dasharray="${item.dash.join(" ")}"` : ""}/>`
287
+ );
288
+ break;
289
+ case "text":
290
+ parts.push(
291
+ `<text x="${item.x}" y="${item.y}"${item.size !== void 0 ? ` font-size="${item.size}"` : ""}${item.weight === "bold" ? ` font-weight="bold"` : ""} fill="${esc(item.fill ?? "#111")}"${item.anchor !== void 0 && item.anchor !== "start" ? ` text-anchor="${item.anchor}"` : ""}>${esc(item.text)}</text>`
292
+ );
293
+ break;
294
+ case "circle":
295
+ parts.push(
296
+ `<circle cx="${item.cx}" cy="${item.cy}" r="${item.r}" fill="${esc(item.fill)}"/>`
297
+ );
298
+ break;
299
+ }
300
+ }
301
+ parts.push("</svg>");
302
+ return parts.join("\n") + "\n";
303
+ };
304
+ var scaleDrawing = (drawing, s) => ({
305
+ width: drawing.width * s,
306
+ height: drawing.height * s,
307
+ ...drawing.background !== void 0 ? { background: drawing.background } : {},
308
+ items: drawing.items.map((item) => {
309
+ switch (item.kind) {
310
+ case "rect":
311
+ return {
312
+ ...item,
313
+ x: item.x * s,
314
+ y: item.y * s,
315
+ w: item.w * s,
316
+ h: item.h * s,
317
+ ...item.rx !== void 0 ? { rx: item.rx * s } : {},
318
+ ...item.strokeWidth !== void 0 ? { strokeWidth: item.strokeWidth * s } : {}
319
+ };
320
+ case "line":
321
+ return {
322
+ ...item,
323
+ x1: item.x1 * s,
324
+ y1: item.y1 * s,
325
+ x2: item.x2 * s,
326
+ y2: item.y2 * s,
327
+ ...item.strokeWidth !== void 0 ? { strokeWidth: item.strokeWidth * s } : {},
328
+ ...item.dash !== void 0 ? { dash: item.dash.map((d) => d * s) } : {}
329
+ };
330
+ case "path":
331
+ return {
332
+ ...item,
333
+ d: scalePathData(item.d, s),
334
+ ...item.strokeWidth !== void 0 ? { strokeWidth: item.strokeWidth * s } : {},
335
+ ...item.dash !== void 0 ? { dash: item.dash.map((d) => d * s) } : {}
336
+ };
337
+ case "text":
338
+ return {
339
+ ...item,
340
+ x: item.x * s,
341
+ y: item.y * s,
342
+ size: (item.size ?? 12) * s
343
+ };
344
+ case "circle":
345
+ return { ...item, cx: item.cx * s, cy: item.cy * s, r: item.r * s };
346
+ }
347
+ })
348
+ });
349
+ var scalePathData = (d, s) => d.replace(/-?\d+(\.\d+)?/g, (n) => String(round2(Number(n) * s)));
350
+ var round2 = (n) => Math.round(n * 100) / 100;
351
+
352
+ // src/svg.ts
353
+ import { isPinEndpoint as isPinEndpoint3 } from "@grayhaven/nerve";
354
+ var BOX_W = 180;
355
+ var ROW_H = 20;
356
+ var HEADER_H = 40;
357
+ var MARGIN = 48;
358
+ var COL_GAP = 380;
359
+ var V_GAP = 48;
360
+ var TITLE_H = 64;
361
+ var strokeFor = (color) => {
362
+ if (color === void 0) return "#888888";
363
+ const map = {
364
+ white: "#b8b8b8",
365
+ yellow: "#c9a800",
366
+ black: "#222222"
367
+ };
368
+ return map[color.toLowerCase()] ?? color;
369
+ };
370
+ var schematicDrawing = (hir) => {
371
+ const left = [];
372
+ const right = [];
373
+ hir.connectors.forEach((c, i) => (i % 2 === 0 ? left : right).push(c));
374
+ const place = (column, side, x) => {
375
+ let y = TITLE_H + MARGIN;
376
+ return column.map((c) => {
377
+ const height2 = HEADER_H + c.pins.length * ROW_H + 8;
378
+ const pinY = new Map(
379
+ c.pins.map((p, i) => [p.pin, y + HEADER_H + (i + 0.5) * ROW_H])
380
+ );
381
+ const placed2 = { ref: c.ref, x, y, height: height2, side, pinY };
382
+ y += height2 + V_GAP;
383
+ return placed2;
384
+ });
385
+ };
386
+ const rightX = MARGIN + BOX_W + COL_GAP;
387
+ const placed = new Map(
388
+ [...place(left, "left", MARGIN), ...place(right, "right", rightX)].map(
389
+ (p) => [p.ref, p]
390
+ )
391
+ );
392
+ const spliceCenterX = MARGIN + BOX_W + COL_GAP / 2;
393
+ const splicePos = new Map(
394
+ hir.splices.map((s, i) => [
395
+ s.id,
396
+ { x: spliceCenterX, y: TITLE_H + MARGIN + 60 + i * 70 }
397
+ ])
398
+ );
399
+ const width = rightX + BOX_W + MARGIN;
400
+ const height = Math.max(
401
+ ...[...placed.values()].map((p) => p.y + p.height),
402
+ ...[...splicePos.values()].map((p) => p.y + 40),
403
+ TITLE_H + MARGIN
404
+ ) + MARGIN;
405
+ const errorWires = new Set(
406
+ hir.diagnostics.filter((d) => d.severity === "error" && d.target?.startsWith("wire:")).map((d) => d.target.slice("wire:".length))
407
+ );
408
+ const items = [
409
+ // Title block
410
+ {
411
+ kind: "text",
412
+ x: MARGIN,
413
+ y: 28,
414
+ text: hir.harness.id,
415
+ size: 18,
416
+ weight: "bold",
417
+ fill: "#111"
418
+ },
419
+ {
420
+ kind: "text",
421
+ x: MARGIN,
422
+ y: 48,
423
+ text: `rev ${hir.harness.revision} \xB7 units ${hir.harness.units} \xB7 HIR ${hir.schemaVersion}`,
424
+ fill: "#555"
425
+ }
426
+ ];
427
+ const anchorOf = (e) => {
428
+ if (isPinEndpoint3(e)) {
429
+ const p = placed.get(e.connector);
430
+ const y = p?.pinY.get(e.pin);
431
+ if (p === void 0 || y === void 0) return void 0;
432
+ return p.side === "left" ? { x: p.x + BOX_W, y, dir: 1 } : { x: p.x, y, dir: -1 };
433
+ }
434
+ const s = splicePos.get(e.splice);
435
+ return s !== void 0 ? { x: s.x, y: s.y, dir: 0 } : void 0;
436
+ };
437
+ for (const w of hir.wires) {
438
+ const a = anchorOf(w.from);
439
+ const b = anchorOf(w.to);
440
+ if (a === void 0 || b === void 0) {
441
+ continue;
442
+ }
443
+ const { x: x1, y: y1 } = a;
444
+ const { x: x2, y: y2 } = b;
445
+ const dx = Math.max(60, Math.abs(x2 - x1) / 3);
446
+ const dir1 = a.dir !== 0 ? a.dir : x2 > x1 ? 1 : -1;
447
+ const dir2 = b.dir !== 0 ? b.dir : x1 > x2 ? 1 : -1;
448
+ const c1 = x1 + dir1 * dx;
449
+ const c2 = x2 + dir2 * dx;
450
+ const isError = errorWires.has(w.id);
451
+ items.push({
452
+ kind: "path",
453
+ d: `M ${x1} ${y1} C ${c1} ${y1}, ${c2} ${y2}, ${x2} ${y2}`,
454
+ stroke: isError ? "#d11" : strokeFor(w.color),
455
+ strokeWidth: 2,
456
+ ...isError ? { dash: [6, 3] } : {}
457
+ });
458
+ const annotation = [w.id, w.gauge, w.twistGroup !== void 0 ? "twisted" : void 0].filter((s) => s !== void 0).join(" \xB7 ");
459
+ items.push({
460
+ kind: "text",
461
+ x: (x1 + x2) / 2,
462
+ y: (y1 + y2) / 2 - 6,
463
+ text: annotation,
464
+ fill: isError ? "#d11" : "#333",
465
+ anchor: "middle"
466
+ });
467
+ }
468
+ for (const c of hir.connectors) {
469
+ const p = placed.get(c.ref);
470
+ if (p === void 0) continue;
471
+ items.push(
472
+ {
473
+ kind: "rect",
474
+ x: p.x,
475
+ y: p.y,
476
+ w: BOX_W,
477
+ h: p.height,
478
+ rx: 6,
479
+ fill: "#ffffff",
480
+ stroke: "#333",
481
+ strokeWidth: 1.5
482
+ },
483
+ { kind: "text", x: p.x + 10, y: p.y + 18, text: c.ref, weight: "bold", fill: "#111" },
484
+ {
485
+ kind: "text",
486
+ x: p.x + 10,
487
+ y: p.y + 33,
488
+ text: `${c.mpn}${c.gender !== void 0 ? ` \xB7 ${c.gender}` : ""}`,
489
+ size: 10,
490
+ fill: "#777"
491
+ },
492
+ {
493
+ kind: "line",
494
+ x1: p.x,
495
+ y1: p.y + HEADER_H - 2,
496
+ x2: p.x + BOX_W,
497
+ y2: p.y + HEADER_H - 2,
498
+ stroke: "#ddd"
499
+ }
500
+ );
501
+ for (const pin of c.pins) {
502
+ const y = p.pinY.get(pin.pin);
503
+ const anchorX = p.side === "left" ? p.x + BOX_W : p.x;
504
+ items.push(
505
+ { kind: "circle", cx: anchorX, cy: y, r: 3, fill: "#333" },
506
+ { kind: "text", x: p.x + 10, y: y + 4, text: pin.pin, fill: "#111" },
507
+ { kind: "text", x: p.x + 34, y: y + 4, text: pin.signal ?? "", fill: "#555" }
508
+ );
509
+ }
510
+ }
511
+ for (const s of hir.splices) {
512
+ const pos = splicePos.get(s.id);
513
+ if (pos === void 0) continue;
514
+ items.push(
515
+ { kind: "circle", cx: pos.x, cy: pos.y, r: 6, fill: "#333" },
516
+ {
517
+ kind: "text",
518
+ x: pos.x,
519
+ y: pos.y - 12,
520
+ text: `${s.id}${s.type !== void 0 ? ` \xB7 ${s.type}` : ""}`,
521
+ size: 11,
522
+ fill: "#333",
523
+ anchor: "middle"
524
+ }
525
+ );
526
+ }
527
+ return { width, height, background: "#fafafa", items };
528
+ };
529
+ var schematicSvg = (hir) => renderSvg(schematicDrawing(hir));
530
+
531
+ // src/board.ts
532
+ var SCALE = 0.8;
533
+ var MARGIN2 = 48;
534
+ var TITLE_H2 = 64;
535
+ var TRUNK_GAP = 150;
536
+ var DEFAULT_LEN_PX = 240;
537
+ var NODE_W = 64;
538
+ var NODE_H = 26;
539
+ var lengthPx = (mm) => mm !== void 0 ? Math.max(120, mm * SCALE) : DEFAULT_LEN_PX;
540
+ var boardDrawing = (hir) => {
541
+ const items = [
542
+ {
543
+ kind: "text",
544
+ x: MARGIN2,
545
+ y: 28,
546
+ text: `${hir.harness.id} \u2014 harness board`,
547
+ size: 18,
548
+ weight: "bold",
549
+ fill: "#111"
550
+ },
551
+ {
552
+ kind: "text",
553
+ x: MARGIN2,
554
+ y: 48,
555
+ text: `rev ${hir.harness.revision} \xB7 units ${hir.harness.units} \xB7 scale ${SCALE} px/${hir.harness.units}`,
556
+ fill: "#555"
557
+ }
558
+ ];
559
+ const roots = hir.branches.filter((b) => b.parent === void 0);
560
+ const children = (parent) => hir.branches.filter((b) => b.parent === parent);
561
+ let maxX = MARGIN2 + 400;
562
+ let y = TITLE_H2 + MARGIN2 + NODE_H;
563
+ const drawEndpoint = (x, cy, ref) => {
564
+ items.push(
565
+ {
566
+ kind: "rect",
567
+ x: x - NODE_W / 2,
568
+ y: cy - NODE_H / 2,
569
+ w: NODE_W,
570
+ h: NODE_H,
571
+ rx: 4,
572
+ fill: "#ffffff",
573
+ stroke: "#333",
574
+ strokeWidth: 1.5
575
+ },
576
+ {
577
+ kind: "text",
578
+ x,
579
+ y: cy + 4,
580
+ text: ref,
581
+ weight: "bold",
582
+ fill: "#111",
583
+ anchor: "middle"
584
+ }
585
+ );
586
+ };
587
+ const drawBranch = (branch, x0, cy, depth) => {
588
+ const len = lengthPx(branch.nominalLength);
589
+ const x1 = x0 + len;
590
+ maxX = Math.max(maxX, x1 + NODE_W);
591
+ items.push({
592
+ kind: "line",
593
+ x1: x0,
594
+ y1: cy,
595
+ x2: x1,
596
+ y2: cy,
597
+ stroke: "#444",
598
+ strokeWidth: 5
599
+ });
600
+ const path = branch.path;
601
+ path.forEach((ref, i) => {
602
+ const t = path.length > 1 ? i / (path.length - 1) : 0;
603
+ drawEndpoint(x0 + t * len, cy, ref);
604
+ });
605
+ const callout = [
606
+ branch.id,
607
+ branch.sleeve !== void 0 ? `sleeve: ${branch.sleeve}` : void 0
608
+ ].filter((s) => s !== void 0).join(" \xB7 ");
609
+ items.push({
610
+ kind: "text",
611
+ x: x0 + len / 2,
612
+ y: cy - NODE_H / 2 - 8,
613
+ text: callout,
614
+ fill: "#333",
615
+ anchor: "middle"
616
+ });
617
+ if (branch.nominalLength !== void 0) {
618
+ const dimY = cy + NODE_H / 2 + 16;
619
+ items.push(
620
+ { kind: "line", x1: x0, y1: dimY, x2: x1, y2: dimY, stroke: "#999" },
621
+ { kind: "line", x1: x0, y1: dimY - 4, x2: x0, y2: dimY + 4, stroke: "#999" },
622
+ { kind: "line", x1, y1: dimY - 4, x2: x1, y2: dimY + 4, stroke: "#999" },
623
+ {
624
+ kind: "text",
625
+ x: x0 + len / 2,
626
+ y: dimY + 14,
627
+ text: `${branch.nominalLength} ${hir.harness.units} nominal`,
628
+ size: 11,
629
+ fill: "#777",
630
+ anchor: "middle"
631
+ }
632
+ );
633
+ }
634
+ for (const label of hir.labels.filter((l) => l.attachTo === branch.id)) {
635
+ const fromEnd = label.offsetFrom !== void 0 && label.offsetFrom === path[path.length - 1];
636
+ const off = (label.distance ?? 0) * SCALE;
637
+ const lx = fromEnd ? x1 - off : x0 + off;
638
+ const flagText = `${label.id}: ${label.text}`;
639
+ items.push(
640
+ { kind: "line", x1: lx, y1: cy, x2: lx, y2: cy - 34, stroke: "#b07a00" },
641
+ {
642
+ kind: "rect",
643
+ x: lx,
644
+ y: cy - 50,
645
+ w: 16 + flagText.length * 6.7,
646
+ h: 16,
647
+ fill: "#fff3d6",
648
+ stroke: "#b07a00",
649
+ strokeWidth: 1
650
+ },
651
+ {
652
+ kind: "text",
653
+ x: lx + 8,
654
+ y: cy - 38,
655
+ text: flagText,
656
+ size: 11,
657
+ fill: "#8a5a00"
658
+ }
659
+ );
660
+ }
661
+ for (const s of hir.splices.filter((sp) => sp.branch === branch.id)) {
662
+ const sx = x0 + Math.min((s.location ?? 0) * SCALE, len);
663
+ items.push(
664
+ { kind: "circle", cx: sx, cy, r: 6, fill: "#333" },
665
+ {
666
+ kind: "text",
667
+ x: sx,
668
+ y: cy + NODE_H / 2 + 30,
669
+ text: `${s.id}${s.type !== void 0 ? ` (${s.type})` : ""}`,
670
+ size: 11,
671
+ fill: "#333",
672
+ anchor: "middle"
673
+ }
674
+ );
675
+ }
676
+ let childY = cy;
677
+ for (const child of children(branch.id)) {
678
+ const bx = x0 + Math.min((child.breakoutDistance ?? 0) * SCALE, len);
679
+ childY += TRUNK_GAP * 0.7;
680
+ items.push({
681
+ kind: "line",
682
+ x1: bx,
683
+ y1: cy,
684
+ x2: bx + 24,
685
+ y2: childY,
686
+ stroke: "#444",
687
+ strokeWidth: 3
688
+ });
689
+ childY = drawBranch(child, bx + 24, childY, depth + 1);
690
+ }
691
+ return childY;
692
+ };
693
+ if (roots.length === 0) {
694
+ items.push({
695
+ kind: "text",
696
+ x: MARGIN2,
697
+ y,
698
+ text: "No branches defined \u2014 add branch(...) entries to lay out the board.",
699
+ fill: "#777"
700
+ });
701
+ y += 24;
702
+ }
703
+ for (const root of roots) {
704
+ y = drawBranch(root, MARGIN2 + NODE_W / 2, y + 30, 0) + TRUNK_GAP;
705
+ }
706
+ return {
707
+ width: maxX + MARGIN2,
708
+ height: y + MARGIN2,
709
+ background: "#fafafa",
710
+ items
711
+ };
712
+ };
713
+ var boardSvg = (hir) => renderSvg(boardDrawing(hir));
714
+
715
+ // src/instructions.ts
716
+ import { isPinEndpoint as isPinEndpoint4 } from "@grayhaven/nerve";
717
+ var assemblyInstructions = (hir) => {
718
+ const lines = [];
719
+ const section = (title) => {
720
+ lines.push("", title, "-".repeat(title.length));
721
+ };
722
+ let step = 0;
723
+ const add = (text) => {
724
+ step += 1;
725
+ lines.push(`${String(step).padStart(2, " ")}. ${text}`);
726
+ };
727
+ lines.push(
728
+ `ASSEMBLY INSTRUCTIONS \u2014 ${hir.harness.id} rev ${hir.harness.revision}`,
729
+ `Units: ${hir.harness.units}. Generated from HIR ${hir.schemaVersion}; every step references design object IDs.`
730
+ );
731
+ section("Materials");
732
+ for (const item of hir.bom) {
733
+ add(`Gather ${item.quantity}x ${item.mpn}${item.description !== void 0 ? ` (${item.description})` : ""} \u2014 used by ${item.usedBy.join(", ")}.`);
734
+ }
735
+ section("Cut wires");
736
+ for (const w of hir.wires) {
737
+ const spec = [w.gauge, w.color].filter((s) => s !== void 0).join(" ");
738
+ const len = w.length !== void 0 ? `${w.length} ${hir.harness.units}${w.lengthTolerance !== void 0 ? ` \xB1${w.lengthTolerance}` : ""}` : "length per board";
739
+ add(`Cut ${w.id}: ${spec || "wire"}, ${len}${w.signal !== void 0 ? ` [${w.signal}]` : ""}.`);
740
+ }
741
+ const twistGroups = /* @__PURE__ */ new Map();
742
+ for (const w of hir.wires) {
743
+ if (w.twistGroup === void 0) continue;
744
+ const list = twistGroups.get(w.twistGroup) ?? [];
745
+ list.push(w.id);
746
+ twistGroups.set(w.twistGroup, list);
747
+ }
748
+ if (twistGroups.size > 0) {
749
+ section("Twist pairs");
750
+ for (const [group, wires] of [...twistGroups.entries()].sort()) {
751
+ add(`Twist ${wires.join(" + ")} together (${group}).`);
752
+ }
753
+ }
754
+ if (hir.splices.length > 0) {
755
+ section("Splices");
756
+ for (const s of hir.splices) {
757
+ const parts = [
758
+ `Splice ${s.id}: join ${s.wires.join(" + ")}`,
759
+ s.type !== void 0 ? `(${s.type}${s.part !== void 0 ? `, ${s.part}` : ""})` : void 0,
760
+ s.branch !== void 0 && s.location !== void 0 ? `at ${s.location} ${hir.harness.units} along ${s.branch}` : void 0,
761
+ s.notes
762
+ ].filter((x) => x !== void 0);
763
+ add(parts.join(" ") + ".");
764
+ }
765
+ }
766
+ section("Populate connectors");
767
+ for (const c of hir.connectors) {
768
+ const wired = hir.wires.filter(
769
+ (w) => [w.from, w.to].some((e) => isPinEndpoint4(e) && e.connector === c.ref)
770
+ );
771
+ add(`Populate ${c.ref} (${c.mpn}${c.gender !== void 0 ? `, ${c.gender}` : ""}):`);
772
+ for (const w of wired) {
773
+ const end = [w.from, w.to].find(
774
+ (e) => isPinEndpoint4(e) && e.connector === c.ref
775
+ );
776
+ if (end === void 0 || !isPinEndpoint4(end)) continue;
777
+ lines.push(` cavity ${end.pin}: ${w.id}${w.signal !== void 0 ? ` (${w.signal})` : ""}`);
778
+ }
779
+ }
780
+ if (hir.branches.length > 0) {
781
+ section("Branch assembly");
782
+ for (const b of hir.branches) {
783
+ const parts = [
784
+ `Route branch ${b.id} (${b.path.join(" \u2192 ")})`,
785
+ b.nominalLength !== void 0 ? `${b.nominalLength} ${hir.harness.units} nominal` : void 0,
786
+ b.sleeve !== void 0 ? `sleeve with ${b.sleeve}` : void 0
787
+ ].filter((s) => s !== void 0);
788
+ add(parts.join("; ") + ".");
789
+ }
790
+ }
791
+ if (hir.labels.length > 0) {
792
+ section("Apply labels");
793
+ for (const l of hir.labels) {
794
+ const placement = l.offsetFrom !== void 0 && l.distance !== void 0 ? ` ${l.distance} ${hir.harness.units} from ${l.offsetFrom}` : "";
795
+ add(`Apply ${l.id} "${l.text}" on ${l.attachTo}${placement}.`);
796
+ }
797
+ }
798
+ section("Inspection");
799
+ add("Verify every cavity against the pinout table; check seating/lock engagement.");
800
+ add("Verify wire colors and labels against the schedule.");
801
+ add("Verify branch lengths and sleeve coverage against the board drawing.");
802
+ section("Test");
803
+ add("Run the continuity-test procedure (tests.csv / test-plan.json); record results per test ID.");
804
+ return lines.join("\n") + "\n";
805
+ };
806
+
807
+ // src/pdf.ts
808
+ import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
809
+ import { hasErrors } from "@grayhaven/nerve";
810
+ var PAGE_W = 792;
811
+ var PAGE_H = 612;
812
+ var MARGIN3 = 40;
813
+ var NAMED_COLORS = {
814
+ red: [0.85, 0.1, 0.1],
815
+ black: [0.13, 0.13, 0.13],
816
+ blue: [0.1, 0.2, 0.85],
817
+ green: [0.1, 0.6, 0.2],
818
+ white: [0.72, 0.72, 0.72],
819
+ yellow: [0.79, 0.66, 0],
820
+ orange: [0.9, 0.5, 0.1],
821
+ gray: [0.5, 0.5, 0.5],
822
+ grey: [0.5, 0.5, 0.5],
823
+ brown: [0.5, 0.33, 0.2],
824
+ purple: [0.5, 0.2, 0.7],
825
+ pink: [0.9, 0.4, 0.6]
826
+ };
827
+ var parseColor = (color) => {
828
+ if (color === void 0) return rgb(0.4, 0.4, 0.4);
829
+ const named = NAMED_COLORS[color.toLowerCase()];
830
+ if (named !== void 0) return rgb(...named);
831
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color)?.[1];
832
+ if (hex !== void 0) {
833
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
834
+ return rgb(
835
+ parseInt(full.slice(0, 2), 16) / 255,
836
+ parseInt(full.slice(2, 4), 16) / 255,
837
+ parseInt(full.slice(4, 6), 16) / 255
838
+ );
839
+ }
840
+ return rgb(0.4, 0.4, 0.4);
841
+ };
842
+ var safe = (s) => s.replace(/→/g, "->").replace(/[^\x20-\x7E\xA0-\xFF·±—–]/g, "?");
843
+ var drawDrawing = (page, drawing, fonts) => {
844
+ const availW = PAGE_W - 2 * MARGIN3;
845
+ const availH = PAGE_H - 2 * MARGIN3;
846
+ const s = Math.min(availW / drawing.width, availH / drawing.height, 1);
847
+ const d = scaleDrawing(drawing, s);
848
+ const ox = MARGIN3 + (availW - d.width) / 2;
849
+ const oyTop = MARGIN3 + (availH - d.height) / 2;
850
+ const flip = (y) => PAGE_H - oyTop - y;
851
+ for (const item of d.items) {
852
+ switch (item.kind) {
853
+ case "rect":
854
+ page.drawRectangle({
855
+ x: ox + item.x,
856
+ y: flip(item.y + item.h),
857
+ width: item.w,
858
+ height: item.h,
859
+ ...item.fill !== void 0 && item.fill !== "none" ? { color: parseColor(item.fill) } : {},
860
+ ...item.stroke !== void 0 ? {
861
+ borderColor: parseColor(item.stroke),
862
+ borderWidth: item.strokeWidth ?? 1
863
+ } : {}
864
+ });
865
+ break;
866
+ case "line":
867
+ page.drawLine({
868
+ start: { x: ox + item.x1, y: flip(item.y1) },
869
+ end: { x: ox + item.x2, y: flip(item.y2) },
870
+ thickness: item.strokeWidth ?? 1,
871
+ color: parseColor(item.stroke),
872
+ ...item.dash !== void 0 ? { dashArray: [...item.dash] } : {}
873
+ });
874
+ break;
875
+ case "path":
876
+ page.drawSvgPath(item.d, {
877
+ x: ox,
878
+ y: PAGE_H - oyTop,
879
+ borderColor: parseColor(item.stroke),
880
+ borderWidth: item.strokeWidth ?? 1,
881
+ ...item.dash !== void 0 ? { borderDashArray: [...item.dash] } : {}
882
+ });
883
+ break;
884
+ case "text": {
885
+ const font = item.weight === "bold" ? fonts.bold : fonts.mono;
886
+ const size = item.size ?? 12;
887
+ const text = safe(item.text);
888
+ const width = font.widthOfTextAtSize(text, size);
889
+ const x = item.anchor === "middle" ? ox + item.x - width / 2 : item.anchor === "end" ? ox + item.x - width : ox + item.x;
890
+ page.drawText(text, {
891
+ x,
892
+ y: flip(item.y),
893
+ size,
894
+ font,
895
+ color: parseColor(item.fill ?? "#111")
896
+ });
897
+ break;
898
+ }
899
+ case "circle":
900
+ page.drawCircle({
901
+ x: ox + item.cx,
902
+ y: flip(item.cy),
903
+ size: item.r,
904
+ color: parseColor(item.fill)
905
+ });
906
+ break;
907
+ }
908
+ }
909
+ };
910
+ var pageHeader = (page, fonts, hir, title) => {
911
+ page.drawText(safe(title), {
912
+ x: MARGIN3,
913
+ y: PAGE_H - MARGIN3,
914
+ size: 14,
915
+ font: fonts.bold,
916
+ color: rgb(0.07, 0.07, 0.07)
917
+ });
918
+ page.drawText(safe(`${hir.harness.id} \xB7 rev ${hir.harness.revision}`), {
919
+ x: PAGE_W - MARGIN3 - fonts.mono.widthOfTextAtSize(`${hir.harness.id} \xB7 rev ${hir.harness.revision}`, 9),
920
+ y: PAGE_H - MARGIN3,
921
+ size: 9,
922
+ font: fonts.mono,
923
+ color: rgb(0.45, 0.45, 0.45)
924
+ });
925
+ page.drawLine({
926
+ start: { x: MARGIN3, y: PAGE_H - MARGIN3 - 8 },
927
+ end: { x: PAGE_W - MARGIN3, y: PAGE_H - MARGIN3 - 8 },
928
+ thickness: 0.7,
929
+ color: rgb(0.8, 0.8, 0.8)
930
+ });
931
+ return PAGE_H - MARGIN3 - 26;
932
+ };
933
+ var drawTablePages = (doc, fonts, hir, title, table) => {
934
+ const usable = PAGE_W - 2 * MARGIN3;
935
+ const colW = usable / table.headers.length;
936
+ const rowH = 14;
937
+ const size = Math.min(8, colW / 6);
938
+ const maxChars = Math.floor(colW / (size * 0.62));
939
+ let page = doc.addPage([PAGE_W, PAGE_H]);
940
+ let y = pageHeader(page, fonts, hir, title);
941
+ const drawRow = (cells, bold) => {
942
+ cells.forEach((cell, i) => {
943
+ const text = safe(String(cell ?? ""));
944
+ page.drawText(text.length > maxChars ? text.slice(0, maxChars - 1) + "\u2026" : text, {
945
+ x: MARGIN3 + i * colW,
946
+ y,
947
+ size,
948
+ font: bold ? fonts.bold : fonts.mono,
949
+ color: rgb(0.1, 0.1, 0.1)
950
+ });
951
+ });
952
+ y -= rowH;
953
+ };
954
+ drawRow(table.headers, true);
955
+ for (const row of table.rows) {
956
+ if (y < MARGIN3 + rowH) {
957
+ page = doc.addPage([PAGE_W, PAGE_H]);
958
+ y = pageHeader(page, fonts, hir, `${title} (cont.)`);
959
+ drawRow(table.headers, true);
960
+ }
961
+ drawRow(row, false);
962
+ }
963
+ };
964
+ var drawTextPages = (doc, fonts, hir, title, body) => {
965
+ let page = doc.addPage([PAGE_W, PAGE_H]);
966
+ let y = pageHeader(page, fonts, hir, title);
967
+ for (const line of body.split("\n")) {
968
+ if (y < MARGIN3 + 12) {
969
+ page = doc.addPage([PAGE_W, PAGE_H]);
970
+ y = pageHeader(page, fonts, hir, `${title} (cont.)`);
971
+ }
972
+ page.drawText(safe(line), {
973
+ x: MARGIN3,
974
+ y,
975
+ size: 9,
976
+ font: fonts.mono,
977
+ color: rgb(0.1, 0.1, 0.1)
978
+ });
979
+ y -= 12;
980
+ }
981
+ };
982
+ var manufacturingPacketPdf = async (hir, options = {}) => {
983
+ const doc = await PDFDocument.create();
984
+ const epoch = /* @__PURE__ */ new Date("2000-01-01T00:00:00Z");
985
+ doc.setCreationDate(epoch);
986
+ doc.setModificationDate(epoch);
987
+ doc.setProducer("Grayhaven Nerve");
988
+ doc.setCreator("Grayhaven Nerve");
989
+ doc.setTitle(`${hir.harness.id} rev ${hir.harness.revision} \u2014 manufacturing packet`);
990
+ const fonts = {
991
+ regular: await doc.embedFont(StandardFonts.Helvetica),
992
+ bold: await doc.embedFont(StandardFonts.HelveticaBold),
993
+ mono: await doc.embedFont(StandardFonts.Courier)
994
+ };
995
+ const cover = doc.addPage([PAGE_W, PAGE_H]);
996
+ const errors = hir.diagnostics.filter((d) => d.severity === "error").length;
997
+ const warnings = hir.diagnostics.filter((d) => d.severity === "warning").length;
998
+ cover.drawText("GRAYHAVEN NERVE", {
999
+ x: MARGIN3,
1000
+ y: PAGE_H - 80,
1001
+ size: 12,
1002
+ font: fonts.bold,
1003
+ color: rgb(0.45, 0.45, 0.45)
1004
+ });
1005
+ cover.drawText("MANUFACTURING PACKET", {
1006
+ x: MARGIN3,
1007
+ y: PAGE_H - 110,
1008
+ size: 26,
1009
+ font: fonts.bold,
1010
+ color: rgb(0.07, 0.07, 0.07)
1011
+ });
1012
+ const fields = [
1013
+ ["Harness", hir.harness.id],
1014
+ ["Revision", hir.harness.revision],
1015
+ ["Units", hir.harness.units],
1016
+ ["HIR schema", hir.schemaVersion],
1017
+ ["Connectors", String(hir.connectors.length)],
1018
+ ["Wires", String(hir.wires.length)],
1019
+ ["Branches", String(hir.branches.length)],
1020
+ ["Labels", String(hir.labels.length)],
1021
+ [
1022
+ "Validation",
1023
+ hasErrors(hir.diagnostics) ? `FAILED \u2014 ${errors} error(s)` : `PASSED \u2014 0 errors, ${warnings} warning(s)`
1024
+ ]
1025
+ ];
1026
+ let fy = PAGE_H - 160;
1027
+ for (const [key, value] of fields) {
1028
+ cover.drawText(safe(key), { x: MARGIN3, y: fy, size: 11, font: fonts.bold });
1029
+ cover.drawText(safe(value), { x: MARGIN3 + 130, y: fy, size: 11, font: fonts.mono });
1030
+ fy -= 20;
1031
+ }
1032
+ cover.drawText("Revision history", { x: MARGIN3, y: fy - 16, size: 11, font: fonts.bold });
1033
+ cover.drawText(safe(`rev ${hir.harness.revision} \u2014 this release`), {
1034
+ x: MARGIN3 + 130,
1035
+ y: fy - 16,
1036
+ size: 11,
1037
+ font: fonts.mono
1038
+ });
1039
+ const schematicPage = doc.addPage([PAGE_W, PAGE_H]);
1040
+ drawDrawing(schematicPage, schematicDrawing(hir), fonts);
1041
+ const boardPage = doc.addPage([PAGE_W, PAGE_H]);
1042
+ drawDrawing(boardPage, boardDrawing(hir), fonts);
1043
+ drawTablePages(doc, fonts, hir, "Bill of Materials", bomTable(hir));
1044
+ drawTablePages(doc, fonts, hir, "Wire Cut List", cutListTable(hir, options));
1045
+ drawTablePages(doc, fonts, hir, "Label Schedule", labelScheduleTable(hir));
1046
+ drawTablePages(doc, fonts, hir, "Continuity Test Procedure", testPlanTable(generateTestPlan(hir)));
1047
+ drawTextPages(doc, fonts, hir, "Assembly Instructions", assemblyInstructions(hir));
1048
+ return doc.save();
1049
+ };
1050
+
1051
+ // src/packet.ts
1052
+ import { zipSync, strToU8 } from "fflate";
1053
+ import { hasErrors as hasErrors2 } from "@grayhaven/nerve";
1054
+ var coverSheet = (hir) => {
1055
+ const errors = hir.diagnostics.filter((d) => d.severity === "error").length;
1056
+ const warnings = hir.diagnostics.filter((d) => d.severity === "warning").length;
1057
+ return [
1058
+ "GRAYHAVEN NERVE \u2014 MANUFACTURING PACKET",
1059
+ "",
1060
+ `Harness: ${hir.harness.id}`,
1061
+ `Revision: ${hir.harness.revision}`,
1062
+ `Units: ${hir.harness.units}`,
1063
+ `HIR schema: ${hir.schemaVersion}`,
1064
+ `Connectors: ${hir.connectors.length}`,
1065
+ `Wires: ${hir.wires.length}`,
1066
+ `Validation: ${errors} error(s), ${warnings} warning(s)`,
1067
+ "",
1068
+ "Contents:",
1069
+ " manufacturing-packet.pdf printable packet (readable without the app)",
1070
+ " harness.json machine-readable HIR",
1071
+ " schematic.svg wiring diagram",
1072
+ " board.svg harness board / nailboard view",
1073
+ " bom.csv bill of materials",
1074
+ " cut-list.csv wire cut list",
1075
+ " labels.csv label schedule",
1076
+ " tests.csv continuity-test procedure",
1077
+ " test-plan.json machine-readable test plan",
1078
+ " assembly-instructions.txt build steps",
1079
+ ""
1080
+ ].join("\n");
1081
+ };
1082
+ var buildPacket = async (hir, options = {}) => {
1083
+ const plan = generateTestPlan(hir);
1084
+ const files = /* @__PURE__ */ new Map([
1085
+ ["COVER.txt", coverSheet(hir)],
1086
+ ["manufacturing-packet.pdf", await manufacturingPacketPdf(hir, options)],
1087
+ ["harness.json", JSON.stringify(hir, null, 2) + "\n"],
1088
+ ["schematic.svg", schematicSvg(hir)],
1089
+ ["board.svg", boardSvg(hir)],
1090
+ ["bom.csv", bomCsv(hir)],
1091
+ ["cut-list.csv", cutListCsv(hir, options)],
1092
+ ["labels.csv", labelScheduleCsv(hir)],
1093
+ ["tests.csv", testPlanCsv(plan)],
1094
+ ["test-plan.json", testPlanJson(hir)],
1095
+ ["assembly-instructions.txt", assemblyInstructions(hir)]
1096
+ ]);
1097
+ const ZIP_EPOCH = new Date(1980, 5, 1);
1098
+ const zipInput = {};
1099
+ for (const [name, contents] of files) {
1100
+ zipInput[name] = [
1101
+ typeof contents === "string" ? strToU8(contents) : contents,
1102
+ { level: 6, mtime: ZIP_EPOCH }
1103
+ ];
1104
+ }
1105
+ return { files, zip: zipSync(zipInput) };
1106
+ };
1107
+ var canRelease = (hir) => !hasErrors2(hir.diagnostics);
1108
+ export {
1109
+ assemblyInstructions,
1110
+ boardDrawing,
1111
+ boardSvg,
1112
+ bomCsv,
1113
+ bomTable,
1114
+ buildPacket,
1115
+ canRelease,
1116
+ coverage,
1117
+ cutListCsv,
1118
+ cutListTable,
1119
+ generateTestPlan,
1120
+ labelScheduleCsv,
1121
+ labelScheduleTable,
1122
+ manufacturingPacketPdf,
1123
+ renderSvg,
1124
+ scaleDrawing,
1125
+ schematicDrawing,
1126
+ schematicSvg,
1127
+ testPlanCsv,
1128
+ testPlanJson,
1129
+ testPlanTable,
1130
+ toCsv
1131
+ };