@tscircuit/cli 0.1.1630 → 0.1.1632

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.
@@ -2674,7 +2674,7 @@ var renderCircuitJsonTo3dPng = async (circuitJson, options = {}) => {
2674
2674
 
2675
2675
  // lib/shared/process-snapshot-file.ts
2676
2676
  import {
2677
- convertCircuitJsonToPcbSvg,
2677
+ convertCircuitJsonToPcbSvg as convertCircuitJsonToPcbSvg2,
2678
2678
  convertCircuitJsonToSchematicSvg
2679
2679
  } from "circuit-to-svg";
2680
2680
 
@@ -2801,8 +2801,23 @@ var import_make_vfs = __toESM(require_dist(), 1);
2801
2801
  // lib/shared/autorouter-diagnostics.ts
2802
2802
  import fs from "node:fs";
2803
2803
  import path from "node:path";
2804
+ import { convertCircuitJsonToPcbSvg } from "circuit-to-svg";
2805
+
2806
+ // lib/shared/convert-svg-to-png.ts
2807
+ import { Resvg } from "@resvg/resvg-js";
2808
+ var convertSvgToPngBuffer = (svg) => {
2809
+ const resvg = new Resvg(svg, {
2810
+ fitTo: {
2811
+ mode: "original"
2812
+ }
2813
+ });
2814
+ return resvg.render().asPng();
2815
+ };
2816
+
2817
+ // lib/shared/autorouter-diagnostics.ts
2804
2818
  var DEFAULT_DEBUG_DIR = path.join("dist", "autorouter-debug");
2805
2819
  var PROGRESS_LOG_INTERVAL_MS = 1e4;
2820
+ var CIRCUIT_JSON_ID_PATTERN = /\b(?:source|pcb|schematic|subcircuit|cad|simulation)_[a-z0-9_]*_\d+\b/gi;
2806
2821
  var parseAutorouterTimeout = (duration) => {
2807
2822
  const trimmed = duration.trim();
2808
2823
  const match = trimmed.match(/^(\d+(?:\.\d+)?)(ms|s|m)?$/);
@@ -2846,6 +2861,7 @@ class AutorouterDiagnostics {
2846
2861
  traceCountBySubcircuit = new Map;
2847
2862
  activePhase = null;
2848
2863
  completedPhaseTraces = [];
2864
+ hasWrittenPlacementSnapshot = false;
2849
2865
  summary = [];
2850
2866
  rootCircuit;
2851
2867
  constructor(options = {}) {
@@ -2878,7 +2894,6 @@ class AutorouterDiagnostics {
2878
2894
  const phaseLabel = this.getPhaseLabel(this.activePhase);
2879
2895
  const message = `Autorouter timeout after ${this.formatElapsed(elapsedMs)} in ${phaseLabel}.`;
2880
2896
  this.log(kleur_default.red(message));
2881
- this.log(`Wrote debug artifact: ${path.relative(process.cwd(), artifactPath)}`);
2882
2897
  throw new AutorouterPhaseTimeoutError(message, artifactPath);
2883
2898
  }
2884
2899
  finalize(circuitJson) {
@@ -2905,7 +2920,7 @@ class AutorouterDiagnostics {
2905
2920
  const previousTraceCount = event.previousTraceCount ?? this.traceCountBySubcircuit.get(subcircuitId) ?? 0;
2906
2921
  this.activePhase = {
2907
2922
  subcircuitId,
2908
- componentDisplayName: event.componentDisplayName ?? subcircuitId,
2923
+ componentDisplayName: event.componentDisplayName ?? "subcircuit",
2909
2924
  routingPhaseIndex,
2910
2925
  phaseOrdinal,
2911
2926
  phaseCount: event.phaseCount,
@@ -2920,6 +2935,11 @@ class AutorouterDiagnostics {
2920
2935
  hasLoggedStart: false,
2921
2936
  longRunningLoggingStarted: false
2922
2937
  };
2938
+ if (this.options.enabled && !this.hasWrittenPlacementSnapshot) {
2939
+ const placementCircuitJson = this.getCurrentCircuitJson().filter((element) => !this.isRouteElement(element));
2940
+ this.writePngSnapshot("placement-unrouted.png", placementCircuitJson);
2941
+ this.hasWrittenPlacementSnapshot = true;
2942
+ }
2923
2943
  if (this.options.enabled) {
2924
2944
  this.logPhaseStart(this.activePhase);
2925
2945
  }
@@ -2982,6 +3002,9 @@ class AutorouterDiagnostics {
2982
3002
  if (this.shouldDumpSuccessfulOutput(activePhase.routingPhaseIndex)) {
2983
3003
  this.writeJson(this.getPhaseFileName(activePhase, "output.traces.json"), outputSrj?.traces ?? []);
2984
3004
  }
3005
+ if (this.options.enabled) {
3006
+ this.writePngSnapshot(`phase-${activePhase.routingPhaseIndex}-routed.png`, this.getCircuitJsonWithCompletedPhaseTraces());
3007
+ }
2985
3008
  if (this.activePhase === activePhase) {
2986
3009
  this.activePhase = null;
2987
3010
  }
@@ -3010,7 +3033,7 @@ class AutorouterDiagnostics {
3010
3033
  if (!activePhase.hasLoggedStart) {
3011
3034
  this.logPhaseStart(activePhase, "failed");
3012
3035
  }
3013
- this.log(` ${this.getPhaseLabel(activePhase)} error after ${this.formatElapsed(elapsedMs)}: ${error.message}`);
3036
+ this.log(` ${this.getPhaseLabel(activePhase)} error after ${this.formatElapsed(elapsedMs)}: ${this.formatUserFacingText(error.message)}`);
3014
3037
  }
3015
3038
  if (this.shouldDumpFailedInput(activePhase.routingPhaseIndex)) {
3016
3039
  this.writeJson(this.getPhaseFileName(activePhase, "input.simple-route.json"), event.simpleRouteJson ?? activePhase.simpleRouteJson ?? {});
@@ -3086,7 +3109,7 @@ class AutorouterDiagnostics {
3086
3109
  logPhaseStart(activePhase, reason) {
3087
3110
  const reasonText = reason ? ` ${reason}` : "";
3088
3111
  this.log([
3089
- `Autorouting ${activePhase.componentDisplayName} (${activePhase.subcircuitId}) ${this.getPhaseLabel(activePhase)}${reasonText} start:`,
3112
+ `Autorouting ${this.formatUserFacingText(activePhase.componentDisplayName)} ${this.getPhaseLabel(activePhase)}${reasonText} start:`,
3090
3113
  `connections=${activePhase.connectionCount},`,
3091
3114
  `obstacles=${activePhase.obstacleCount},`,
3092
3115
  `previous_traces=${activePhase.previousTraceCount}`,
@@ -3141,8 +3164,54 @@ class AutorouterDiagnostics {
3141
3164
  fs.mkdirSync(debugDir, { recursive: true });
3142
3165
  const filePath = path.join(debugDir, fileName);
3143
3166
  fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
3167
+ this.logArtifact(filePath);
3144
3168
  return filePath;
3145
3169
  }
3170
+ writePngSnapshot(fileName, circuitJson) {
3171
+ const pcbSvg = convertCircuitJsonToPcbSvg(circuitJson);
3172
+ const png = convertSvgToPngBuffer(pcbSvg);
3173
+ const debugDir = path.resolve(this.options.debugDir ?? DEFAULT_DEBUG_DIR);
3174
+ fs.mkdirSync(debugDir, { recursive: true });
3175
+ const filePath = path.join(debugDir, fileName);
3176
+ fs.writeFileSync(filePath, png);
3177
+ this.logArtifact(filePath);
3178
+ }
3179
+ logArtifact(filePath) {
3180
+ this.log(`Wrote debug artifact: ${path.relative(process.cwd(), filePath)}`);
3181
+ }
3182
+ getCircuitJsonWithCompletedPhaseTraces() {
3183
+ const circuitJson = [...this.getCurrentCircuitJson()];
3184
+ const elementIndexById = new Map;
3185
+ for (const [index, element] of circuitJson.entries()) {
3186
+ const id = this.getCircuitElementId(element);
3187
+ if (id)
3188
+ elementIndexById.set(id, index);
3189
+ }
3190
+ for (const trace of this.completedPhaseTraces) {
3191
+ if (!trace || typeof trace !== "object")
3192
+ continue;
3193
+ const element = trace;
3194
+ const id = this.getCircuitElementId(element);
3195
+ const existingIndex = id ? elementIndexById.get(id) : undefined;
3196
+ if (existingIndex === undefined) {
3197
+ circuitJson.push(element);
3198
+ if (id)
3199
+ elementIndexById.set(id, circuitJson.length - 1);
3200
+ } else {
3201
+ circuitJson[existingIndex] = element;
3202
+ }
3203
+ }
3204
+ return circuitJson;
3205
+ }
3206
+ getCircuitElementId(element) {
3207
+ if (typeof element.type !== "string")
3208
+ return;
3209
+ const id = element[`${element.type}_id`];
3210
+ return typeof id === "string" ? id : undefined;
3211
+ }
3212
+ isRouteElement(element) {
3213
+ return element.type === "pcb_trace" || element.type === "pcb_via";
3214
+ }
3146
3215
  getPhaseFileName(activePhase, suffix) {
3147
3216
  const phaseNumber = activePhase.routingPhaseIndex;
3148
3217
  return `phase-${phaseNumber}.${suffix}`;
@@ -3167,10 +3236,61 @@ class AutorouterDiagnostics {
3167
3236
  return parts.join(", ");
3168
3237
  }
3169
3238
  getConnectionNames(simpleRouteJson) {
3170
- return (simpleRouteJson?.connections ?? []).map((connection) => {
3171
- const name = connection.name ?? connection.rootConnectionName ?? connection.source_trace_id;
3172
- return typeof name === "string" ? name : null;
3173
- }).filter((name) => Boolean(name));
3239
+ return [
3240
+ ...new Set((simpleRouteJson?.connections ?? []).map((connection) => [
3241
+ connection.rootConnectionName,
3242
+ connection.name,
3243
+ connection.source_trace_id
3244
+ ].filter((value) => typeof value === "string").map((value) => this.resolveCircuitJsonId(value)).find((value) => value !== null)).filter((name) => name !== null))
3245
+ ];
3246
+ }
3247
+ resolveCircuitJsonId(value) {
3248
+ const circuitJson = this.getCurrentCircuitJson();
3249
+ const element = circuitJson.find((candidate) => Object.entries(candidate).some(([key, candidateValue]) => key.endsWith("_id") && candidateValue === value));
3250
+ if (!element)
3251
+ return value;
3252
+ if (element.type === "source_trace") {
3253
+ if (typeof element.name === "string")
3254
+ return element.name;
3255
+ return this.getTraceSelector(element, circuitJson);
3256
+ }
3257
+ if (element.type === "source_net" && typeof element.name === "string") {
3258
+ return `net.${element.name}`;
3259
+ }
3260
+ if (element.type === "source_port") {
3261
+ return this.getPortSelector(element, circuitJson);
3262
+ }
3263
+ return null;
3264
+ }
3265
+ formatUserFacingText(value) {
3266
+ let formattedValue = value;
3267
+ for (const element of this.getCurrentCircuitJson()) {
3268
+ for (const [key, id] of Object.entries(element)) {
3269
+ if (!key.endsWith("_id") || typeof id !== "string")
3270
+ continue;
3271
+ formattedValue = formattedValue.replaceAll(id, this.resolveCircuitJsonId(id) ?? "internal element");
3272
+ }
3273
+ }
3274
+ return formattedValue.replace(CIRCUIT_JSON_ID_PATTERN, "internal element");
3275
+ }
3276
+ getTraceSelector(trace, circuitJson) {
3277
+ const portIds = trace.connected_source_port_ids;
3278
+ if (!Array.isArray(portIds))
3279
+ return null;
3280
+ const selectors = portIds.map((portId) => typeof portId === "string" ? this.getPortSelectorById(portId, circuitJson) : null).filter((selector) => selector !== null);
3281
+ return selectors.length >= 2 ? selectors.join(" to ") : null;
3282
+ }
3283
+ getPortSelectorById(sourcePortId, circuitJson) {
3284
+ const port = circuitJson.find((element) => element.type === "source_port" && element.source_port_id === sourcePortId);
3285
+ return port ? this.getPortSelector(port, circuitJson) : null;
3286
+ }
3287
+ getPortSelector(port, circuitJson) {
3288
+ const portName = typeof port.most_frequently_referenced_by_name === "string" ? port.most_frequently_referenced_by_name : typeof port.name === "string" ? port.name : null;
3289
+ if (!portName)
3290
+ return null;
3291
+ const component = circuitJson.find((element) => element.type === "source_component" && element.source_component_id === port.source_component_id);
3292
+ const componentName = component && typeof component.name === "string" ? component.name : null;
3293
+ return componentName ? `${componentName}.${portName}` : portName;
3174
3294
  }
3175
3295
  countErrors(simpleRouteJson) {
3176
3296
  const errors = simpleRouteJson?.errors;
@@ -3178,7 +3298,8 @@ class AutorouterDiagnostics {
3178
3298
  }
3179
3299
  getCurrentCircuitJson() {
3180
3300
  try {
3181
- return this.rootCircuit?.db?.toArray?.() ?? [];
3301
+ const circuitJson = this.rootCircuit?.db?.toArray?.();
3302
+ return Array.isArray(circuitJson) ? circuitJson : [];
3182
3303
  } catch {
3183
3304
  return [];
3184
3305
  }
@@ -3421,7 +3542,7 @@ async function generateCircuitJson({
3421
3542
  }
3422
3543
  runner.emit("renderComplete");
3423
3544
  const circuitJson = await runner.getCircuitJson();
3424
- autorouterDiagnostics.finalize(circuitJson);
3545
+ await autorouterDiagnostics.finalize(circuitJson);
3425
3546
  if (saveToFile) {
3426
3547
  debug(`Saving circuit JSON to ${outputPath}`);
3427
3548
  fs4.writeFileSync(outputPath, JSON.stringify(circuitJson, null, 2));
@@ -3637,7 +3758,7 @@ var processSnapshotFile = async ({
3637
3758
  }
3638
3759
  if (!simulationOnly) {
3639
3760
  try {
3640
- pcbSvg = convertCircuitJsonToPcbSvg(circuitJson, {
3761
+ pcbSvg = convertCircuitJsonToPcbSvg2(circuitJson, {
3641
3762
  ...pcbSnapshotSettings,
3642
3763
  layer: pcbLayer
3643
3764
  });
package/dist/lib/index.js CHANGED
@@ -65811,7 +65811,7 @@ var getNodeHandler = (winterSpec, { port, middleware = [] }) => {
65811
65811
  }));
65812
65812
  };
65813
65813
  // package.json
65814
- var version = "0.1.1628";
65814
+ var version = "0.1.1631";
65815
65815
  var package_default = {
65816
65816
  name: "@tscircuit/cli",
65817
65817
  version,
@@ -80118,6 +80118,28 @@ Publish completed with ${uploadResults.succeeded.length} files uploaded and ${up
80118
80118
  var debug2 = Debug2("tscircuit:devserver");
80119
80119
  var BINARY_FILE_EXTENSIONS2 = new Set([".glb", ".png", ".jpeg", ".jpg"]);
80120
80120
  var CONFIG_MODULE_FILENAMES2 = ["tscircuit.config.ts", "tscircuit.config.js"];
80121
+ var CIRCUIT_JSON_ID_PATTERN = /\b(?:source|pcb|schematic|subcircuit|cad|simulation)_[a-z0-9_]*_\d+\b/gi;
80122
+ var formatAutorouterDebugEvent = (event) => {
80123
+ const parts = [
80124
+ typeof event.componentDisplayName === "string" ? `component=${event.componentDisplayName}` : null,
80125
+ typeof event.phase === "string" ? `phase=${event.phase}` : null,
80126
+ typeof event.solverName === "string" ? `solver=${event.solverName}` : null,
80127
+ typeof event.connectionCount === "number" ? `connections=${event.connectionCount}` : null,
80128
+ typeof event.obstacleCount === "number" ? `obstacles=${event.obstacleCount}` : null,
80129
+ typeof event.steps === "number" ? `steps=${event.steps}` : null,
80130
+ typeof event.iteration === "number" ? `iteration=${event.iteration}` : null,
80131
+ typeof event.progress === "number" ? `progress=${Math.round(event.progress * 100)}%` : null,
80132
+ typeof event.iterationsPerSecond === "number" ? `iterations_per_second=${Math.round(event.iterationsPerSecond)}` : null,
80133
+ typeof event.elapsedMs === "number" ? `elapsed=${Math.round(event.elapsedMs)}ms` : null
80134
+ ].filter((part) => part !== null);
80135
+ const error = event.error;
80136
+ const errorMessage = error && typeof error === "object" && "message" in error ? error.message : typeof error === "string" ? error : null;
80137
+ if (typeof errorMessage === "string") {
80138
+ parts.push(`error=${errorMessage.replace(CIRCUIT_JSON_ID_PATTERN, "internal element")}`);
80139
+ }
80140
+ const eventType = typeof event.event_type === "string" ? event.event_type : "autorouting";
80141
+ return `[autorouter] ${eventType}${parts.length > 0 ? ` ${parts.join(" ")}` : ""}`;
80142
+ };
80121
80143
 
80122
80144
  class DevServer {
80123
80145
  port;
@@ -80169,7 +80191,7 @@ class DevServer {
80169
80191
  this.eventsWatcher.on("*", (event) => {
80170
80192
  if (!String(event.event_type).startsWith("autorouting:"))
80171
80193
  return;
80172
- console.log(kleur_default.cyan(`[autorouter] ${event.event_type} ${JSON.stringify(event)}`));
80194
+ console.log(kleur_default.cyan(formatAutorouterDebugEvent(event)));
80173
80195
  });
80174
80196
  }
80175
80197
  this.filesystemWatcher = watch(this.projectDir, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.1.1630",
3
+ "version": "0.1.1632",
4
4
  "main": "dist/cli/main.js",
5
5
  "exports": {
6
6
  ".": "./dist/cli/main.js",