@tscircuit/cli 0.1.1628 → 0.1.1629

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.
@@ -2485,8 +2485,8 @@ export default {
2485
2485
  import { parentPort } from "node:worker_threads";
2486
2486
 
2487
2487
  // lib/shared/process-snapshot-file.ts
2488
- import fs6 from "node:fs";
2489
- import path5 from "node:path";
2488
+ import fs7 from "node:fs";
2489
+ import path6 from "node:path";
2490
2490
 
2491
2491
  // node_modules/circuit-json-to-3d-png/dist/index.js
2492
2492
  function normalizeDir(dir) {
@@ -2776,8 +2776,8 @@ function init(open, close) {
2776
2776
  var kleur_default = $;
2777
2777
 
2778
2778
  // lib/shared/generate-circuit-json.tsx
2779
- import fs3 from "node:fs";
2780
- import path3 from "node:path";
2779
+ import fs4 from "node:fs";
2780
+ import path4 from "node:path";
2781
2781
  import { pathToFileURL } from "node:url";
2782
2782
  import Debug from "debug";
2783
2783
 
@@ -2798,14 +2798,416 @@ var abbreviateStringifyObject = (obj) => {
2798
2798
  // lib/shared/generate-circuit-json.tsx
2799
2799
  var import_make_vfs = __toESM(require_dist(), 1);
2800
2800
 
2801
- // lib/shared/importFromUserLand.ts
2802
- import { createRequire as createRequire2 } from "node:module";
2801
+ // lib/shared/autorouter-diagnostics.ts
2803
2802
  import fs from "node:fs";
2804
2803
  import path from "node:path";
2804
+ var DEFAULT_DEBUG_DIR = path.join("dist", "autorouter-debug");
2805
+ var PROGRESS_LOG_INTERVAL_MS = 1e4;
2806
+ var parseAutorouterTimeout = (duration) => {
2807
+ const trimmed = duration.trim();
2808
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)(ms|s|m)?$/);
2809
+ if (!match) {
2810
+ throw new Error(`Invalid --autorouter-timeout value "${duration}". Use values like "120s", "2m", or "5000ms".`);
2811
+ }
2812
+ const value = Number.parseFloat(match[1]);
2813
+ const unit = match[2] ?? "ms";
2814
+ const multiplier = unit === "m" ? 60000 : unit === "s" ? 1000 : 1;
2815
+ const timeoutMs = Math.round(value * multiplier);
2816
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2817
+ throw new Error("--autorouter-timeout must be greater than zero");
2818
+ }
2819
+ return timeoutMs;
2820
+ };
2821
+ var parseAutorouterDumpSrjMode = (value) => {
2822
+ if (value === undefined || value === false)
2823
+ return;
2824
+ if (value === true || value === "")
2825
+ return "failed";
2826
+ if (value === "all" || value === "failed")
2827
+ return value;
2828
+ const phaseMatch = value.match(/^phase:(\d+)$/);
2829
+ if (phaseMatch)
2830
+ return `phase:${Number.parseInt(phaseMatch[1], 10)}`;
2831
+ throw new Error(`Invalid --autorouter-dump-srj value "${value}". Use "all", "failed", or "phase:<index>".`);
2832
+ };
2833
+
2834
+ class AutorouterPhaseTimeoutError extends Error {
2835
+ debugArtifactPath;
2836
+ constructor(message, debugArtifactPath) {
2837
+ super(message);
2838
+ this.name = "AutorouterPhaseTimeoutError";
2839
+ this.debugArtifactPath = debugArtifactPath;
2840
+ }
2841
+ }
2842
+
2843
+ class AutorouterDiagnostics {
2844
+ options;
2845
+ phaseOrdinalBySubcircuit = new Map;
2846
+ traceCountBySubcircuit = new Map;
2847
+ activePhase = null;
2848
+ completedPhaseTraces = [];
2849
+ summary = [];
2850
+ rootCircuit;
2851
+ constructor(options = {}) {
2852
+ this.options = {
2853
+ ...options,
2854
+ debugDir: options.debugDir ?? DEFAULT_DEBUG_DIR,
2855
+ log: options.log ?? console.log
2856
+ };
2857
+ }
2858
+ get isActive() {
2859
+ return Boolean(this.options.enabled || this.options.timeoutMs || this.options.dumpSrj || this.options.logOnError || this.options.longRunningLogThresholdMs);
2860
+ }
2861
+ attachToRootCircuit(rootCircuit) {
2862
+ if (!this.isActive)
2863
+ return;
2864
+ this.rootCircuit = rootCircuit;
2865
+ rootCircuit.on?.("autorouting:start", (event) => this.handleStart(event));
2866
+ rootCircuit.on?.("autorouting:progress", (event) => this.handleProgress(event));
2867
+ rootCircuit.on?.("autorouting:end", (event) => this.handleEnd(event));
2868
+ rootCircuit.on?.("autorouting:error", (event) => this.handleError(event));
2869
+ }
2870
+ checkTimeout() {
2871
+ this.checkLongRunning();
2872
+ if (!this.options.timeoutMs || !this.activePhase)
2873
+ return;
2874
+ const elapsedMs = performance.now() - this.activePhase.startedAt;
2875
+ if (elapsedMs < this.options.timeoutMs)
2876
+ return;
2877
+ const artifactPath = this.writeTimeoutArtifact(this.activePhase, elapsedMs);
2878
+ const phaseLabel = this.getPhaseLabel(this.activePhase);
2879
+ const message = `Autorouter timeout after ${this.formatElapsed(elapsedMs)} in ${phaseLabel}.`;
2880
+ this.log(kleur_default.red(message));
2881
+ this.log(`Wrote debug artifact: ${path.relative(process.cwd(), artifactPath)}`);
2882
+ throw new AutorouterPhaseTimeoutError(message, artifactPath);
2883
+ }
2884
+ finalize(circuitJson) {
2885
+ if (!this.isActive || this.summary.length === 0 || !this.options.enabled && !this.options.dumpSrj) {
2886
+ return;
2887
+ }
2888
+ this.writeJson("board.meta.json", {
2889
+ type: "autorouter_debug_summary",
2890
+ generatedAt: new Date().toISOString(),
2891
+ phaseCount: this.summary.length,
2892
+ phases: this.summary,
2893
+ circuitJsonElementCount: circuitJson?.length
2894
+ });
2895
+ }
2896
+ handleStart(event) {
2897
+ const simpleRouteJson = event.simpleRouteJson;
2898
+ const subcircuitId = event.subcircuit_id ?? event.subcircuitId ?? "unknown-subcircuit";
2899
+ const previousOrdinal = this.phaseOrdinalBySubcircuit.get(subcircuitId) ?? 0;
2900
+ const phaseOrdinal = event.phaseOrdinal ?? previousOrdinal + 1;
2901
+ this.phaseOrdinalBySubcircuit.set(subcircuitId, phaseOrdinal);
2902
+ const routingPhaseIndex = event.routingPhaseIndex ?? phaseOrdinal - 1;
2903
+ const connectionCount = event.connectionCount ?? simpleRouteJson?.connections?.length ?? 0;
2904
+ const obstacleCount = event.obstacleCount ?? simpleRouteJson?.obstacles?.length ?? 0;
2905
+ const previousTraceCount = event.previousTraceCount ?? this.traceCountBySubcircuit.get(subcircuitId) ?? 0;
2906
+ this.activePhase = {
2907
+ subcircuitId,
2908
+ componentDisplayName: event.componentDisplayName ?? subcircuitId,
2909
+ routingPhaseIndex,
2910
+ phaseOrdinal,
2911
+ phaseCount: event.phaseCount,
2912
+ connectionCount,
2913
+ obstacleCount,
2914
+ previousTraceCount,
2915
+ startedAt: performance.now(),
2916
+ startedAtIso: new Date().toISOString(),
2917
+ simpleRouteJson,
2918
+ routerDescription: this.formatRouter(event),
2919
+ lastProgressLogAt: 0,
2920
+ hasLoggedStart: false,
2921
+ longRunningLoggingStarted: false
2922
+ };
2923
+ if (this.options.enabled) {
2924
+ this.logPhaseStart(this.activePhase);
2925
+ }
2926
+ if (this.shouldDumpInput(routingPhaseIndex)) {
2927
+ this.writeJson(this.getPhaseFileName(this.activePhase, "input.simple-route.json"), simpleRouteJson ?? {});
2928
+ }
2929
+ }
2930
+ handleProgress(event) {
2931
+ if (!this.activePhase)
2932
+ return;
2933
+ const now = performance.now();
2934
+ this.activePhase.lastProgress = event;
2935
+ if (!this.shouldLogPhaseDetails(this.activePhase))
2936
+ return;
2937
+ if (this.activePhase.lastProgressLogAt > 0 && now - this.activePhase.lastProgressLogAt < PROGRESS_LOG_INTERVAL_MS) {
2938
+ return;
2939
+ }
2940
+ this.activePhase.lastProgressLogAt = now;
2941
+ this.logProgress(this.activePhase, event, now);
2942
+ }
2943
+ handleEnd(event) {
2944
+ const activePhase = this.matchActivePhase(event);
2945
+ if (!activePhase)
2946
+ return;
2947
+ const elapsedMs = performance.now() - activePhase.startedAt;
2948
+ const outputSrj = event.simpleRouteJson;
2949
+ const outputTraceCount = outputSrj?.traces?.length ?? 0;
2950
+ const outputJumperCount = outputSrj?.jumpers?.length ?? 0;
2951
+ const errorCount = this.countErrors(outputSrj);
2952
+ const cumulativeTraceCount = activePhase.previousTraceCount + outputTraceCount;
2953
+ this.traceCountBySubcircuit.set(activePhase.subcircuitId, cumulativeTraceCount);
2954
+ this.completedPhaseTraces.push(...outputSrj?.traces ?? []);
2955
+ this.summary.push({
2956
+ subcircuit_id: activePhase.subcircuitId,
2957
+ componentDisplayName: activePhase.componentDisplayName,
2958
+ routingPhaseIndex: activePhase.routingPhaseIndex,
2959
+ phaseOrdinal: activePhase.phaseOrdinal,
2960
+ phaseCount: activePhase.phaseCount,
2961
+ connectionCount: activePhase.connectionCount,
2962
+ obstacleCount: activePhase.obstacleCount,
2963
+ previousTraceCount: activePhase.previousTraceCount,
2964
+ outputTraceCount,
2965
+ outputJumperCount,
2966
+ errorCount,
2967
+ elapsedMs: Math.round(elapsedMs),
2968
+ startedAt: activePhase.startedAtIso,
2969
+ completedAt: new Date().toISOString()
2970
+ });
2971
+ if (this.shouldLogPhaseDetails(activePhase) || this.didCrossLongRunningThreshold(activePhase, elapsedMs)) {
2972
+ if (!activePhase.hasLoggedStart) {
2973
+ this.startLongRunningLogging(activePhase, elapsedMs);
2974
+ }
2975
+ this.logPhaseEnd(activePhase, {
2976
+ outputTraceCount,
2977
+ outputJumperCount,
2978
+ errorCount,
2979
+ elapsedMs
2980
+ });
2981
+ }
2982
+ if (this.shouldDumpSuccessfulOutput(activePhase.routingPhaseIndex)) {
2983
+ this.writeJson(this.getPhaseFileName(activePhase, "output.traces.json"), outputSrj?.traces ?? []);
2984
+ }
2985
+ if (this.activePhase === activePhase) {
2986
+ this.activePhase = null;
2987
+ }
2988
+ }
2989
+ handleError(event) {
2990
+ const activePhase = this.matchActivePhase(event);
2991
+ if (!activePhase)
2992
+ return;
2993
+ const elapsedMs = performance.now() - activePhase.startedAt;
2994
+ const error = this.serializeError(event.error);
2995
+ this.summary.push({
2996
+ subcircuit_id: activePhase.subcircuitId,
2997
+ componentDisplayName: activePhase.componentDisplayName,
2998
+ routingPhaseIndex: activePhase.routingPhaseIndex,
2999
+ phaseOrdinal: activePhase.phaseOrdinal,
3000
+ phaseCount: activePhase.phaseCount,
3001
+ connectionCount: activePhase.connectionCount,
3002
+ obstacleCount: activePhase.obstacleCount,
3003
+ previousTraceCount: activePhase.previousTraceCount,
3004
+ elapsedMs: Math.round(elapsedMs),
3005
+ error,
3006
+ startedAt: activePhase.startedAtIso,
3007
+ completedAt: new Date().toISOString()
3008
+ });
3009
+ if (this.options.enabled || this.options.logOnError) {
3010
+ if (!activePhase.hasLoggedStart) {
3011
+ this.logPhaseStart(activePhase, "failed");
3012
+ }
3013
+ this.log(` ${this.getPhaseLabel(activePhase)} error after ${this.formatElapsed(elapsedMs)}: ${error.message}`);
3014
+ }
3015
+ if (this.shouldDumpFailedInput(activePhase.routingPhaseIndex)) {
3016
+ this.writeJson(this.getPhaseFileName(activePhase, "input.simple-route.json"), event.simpleRouteJson ?? activePhase.simpleRouteJson ?? {});
3017
+ this.writeJson(this.getPhaseFileName(activePhase, "error.json"), {
3018
+ type: "autorouter_phase_error",
3019
+ subcircuit_id: activePhase.subcircuitId,
3020
+ routingPhaseIndex: activePhase.routingPhaseIndex,
3021
+ phaseOrdinal: activePhase.phaseOrdinal,
3022
+ phaseCount: activePhase.phaseCount,
3023
+ elapsedMs: Math.round(elapsedMs),
3024
+ connectionCount: activePhase.connectionCount,
3025
+ obstacleCount: activePhase.obstacleCount,
3026
+ previousTraceCount: activePhase.previousTraceCount,
3027
+ error
3028
+ });
3029
+ }
3030
+ if (this.activePhase === activePhase) {
3031
+ this.activePhase = null;
3032
+ }
3033
+ }
3034
+ writeTimeoutArtifact(activePhase, elapsedMs) {
3035
+ const inputFile = this.getPhaseFileName(activePhase, "input.simple-route.json");
3036
+ const previousTracesFile = this.getPhaseFileName(activePhase, "previous-output.traces.json");
3037
+ const timeoutFile = this.getPhaseFileName(activePhase, "timeout.json");
3038
+ const boardFile = "board.source-and-pcb.circuit.json";
3039
+ this.writeJson(inputFile, activePhase.simpleRouteJson ?? {});
3040
+ this.writeJson(previousTracesFile, this.completedPhaseTraces);
3041
+ this.writeJson(boardFile, this.getCurrentCircuitJson());
3042
+ return this.writeJson(timeoutFile, {
3043
+ type: "autorouter_phase_timeout",
3044
+ subcircuit_id: activePhase.subcircuitId,
3045
+ componentDisplayName: activePhase.componentDisplayName,
3046
+ routingPhaseIndex: activePhase.routingPhaseIndex,
3047
+ phaseOrdinal: activePhase.phaseOrdinal,
3048
+ phaseCount: activePhase.phaseCount,
3049
+ elapsedMs: Math.round(elapsedMs),
3050
+ connectionCount: activePhase.connectionCount,
3051
+ obstacleCount: activePhase.obstacleCount,
3052
+ previousTraceCount: activePhase.previousTraceCount,
3053
+ autorouter: {
3054
+ kind: "default-or-core"
3055
+ },
3056
+ files: {
3057
+ inputSimpleRouteJson: inputFile,
3058
+ previousOutputTraces: previousTracesFile,
3059
+ boardCircuitJson: boardFile
3060
+ },
3061
+ lastProgress: activePhase.lastProgress
3062
+ });
3063
+ }
3064
+ checkLongRunning() {
3065
+ if (!this.activePhase)
3066
+ return;
3067
+ const elapsedMs = performance.now() - this.activePhase.startedAt;
3068
+ if (!this.didCrossLongRunningThreshold(this.activePhase, elapsedMs))
3069
+ return;
3070
+ this.startLongRunningLogging(this.activePhase, elapsedMs);
3071
+ }
3072
+ didCrossLongRunningThreshold(activePhase, elapsedMs) {
3073
+ return Boolean(!this.options.enabled && this.options.longRunningLogThresholdMs && !activePhase.longRunningLoggingStarted && elapsedMs >= this.options.longRunningLogThresholdMs);
3074
+ }
3075
+ startLongRunningLogging(activePhase, elapsedMs) {
3076
+ activePhase.longRunningLoggingStarted = true;
3077
+ this.log(kleur_default.yellow(`Autorouting ${this.getPhaseLabel(activePhase)} has been running for ${this.formatElapsed(elapsedMs)}; enabling phase diagnostics. Re-run with --autorouter-debug for full autorouter logs from the start.`));
3078
+ this.logPhaseStart(activePhase, "long-running");
3079
+ if (activePhase.lastProgress) {
3080
+ this.logProgress(activePhase, activePhase.lastProgress, performance.now());
3081
+ }
3082
+ }
3083
+ shouldLogPhaseDetails(activePhase) {
3084
+ return Boolean(this.options.enabled || activePhase.longRunningLoggingStarted);
3085
+ }
3086
+ logPhaseStart(activePhase, reason) {
3087
+ const reasonText = reason ? ` ${reason}` : "";
3088
+ this.log([
3089
+ `Autorouting ${activePhase.componentDisplayName} (${activePhase.subcircuitId}) ${this.getPhaseLabel(activePhase)}${reasonText} start:`,
3090
+ `connections=${activePhase.connectionCount},`,
3091
+ `obstacles=${activePhase.obstacleCount},`,
3092
+ `previous_traces=${activePhase.previousTraceCount}`,
3093
+ activePhase.routerDescription ? `, ${activePhase.routerDescription}` : ""
3094
+ ].join(" "));
3095
+ const connectionNames = this.getConnectionNames(activePhase.simpleRouteJson);
3096
+ if (connectionNames.length > 0) {
3097
+ this.log(` connections: ${connectionNames.join(", ")}`);
3098
+ }
3099
+ activePhase.hasLoggedStart = true;
3100
+ }
3101
+ logProgress(activePhase, event, now) {
3102
+ const elapsedMs = event.elapsedMs ?? now - activePhase.startedAt;
3103
+ const details = [
3104
+ event.solverName ? `solver=${event.solverName}` : null,
3105
+ event.phase ? `solver_phase=${event.phase}` : null,
3106
+ event.iteration !== undefined ? `iter=${event.iteration}` : event.steps !== undefined ? `steps=${event.steps}` : null,
3107
+ event.progress !== undefined ? `progress=${Math.round(event.progress * 100)}%` : null,
3108
+ event.iterationsPerSecond !== undefined ? `iterations_per_second=${Math.round(event.iterationsPerSecond)}` : null,
3109
+ `elapsed=${this.formatElapsed(elapsedMs)}`
3110
+ ].filter(Boolean);
3111
+ this.log(` ${this.getPhaseLabel(activePhase)} progress: ${details.join(" ")}`);
3112
+ }
3113
+ logPhaseEnd(activePhase, {
3114
+ outputTraceCount,
3115
+ outputJumperCount,
3116
+ errorCount,
3117
+ elapsedMs
3118
+ }) {
3119
+ this.log(` ${this.getPhaseLabel(activePhase)} done: routed_traces=${outputTraceCount}, jumpers=${outputJumperCount}, errors=${errorCount}, elapsed=${this.formatElapsed(elapsedMs)}`);
3120
+ }
3121
+ matchActivePhase(event) {
3122
+ if (!this.activePhase)
3123
+ return null;
3124
+ const subcircuitId = event.subcircuit_id ?? event.subcircuitId;
3125
+ if (subcircuitId && subcircuitId !== this.activePhase.subcircuitId) {
3126
+ return null;
3127
+ }
3128
+ return this.activePhase;
3129
+ }
3130
+ shouldDumpInput(routingPhaseIndex) {
3131
+ return this.options.dumpSrj === "all" || this.options.dumpSrj === `phase:${routingPhaseIndex}`;
3132
+ }
3133
+ shouldDumpSuccessfulOutput(routingPhaseIndex) {
3134
+ return this.options.dumpSrj === "all" || this.options.dumpSrj === `phase:${routingPhaseIndex}`;
3135
+ }
3136
+ shouldDumpFailedInput(routingPhaseIndex) {
3137
+ return this.options.dumpSrj === "all" || this.options.dumpSrj === "failed" || this.options.dumpSrj === `phase:${routingPhaseIndex}`;
3138
+ }
3139
+ writeJson(fileName, value) {
3140
+ const debugDir = path.resolve(this.options.debugDir ?? DEFAULT_DEBUG_DIR);
3141
+ fs.mkdirSync(debugDir, { recursive: true });
3142
+ const filePath = path.join(debugDir, fileName);
3143
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
3144
+ return filePath;
3145
+ }
3146
+ getPhaseFileName(activePhase, suffix) {
3147
+ const phaseNumber = activePhase.routingPhaseIndex;
3148
+ return `phase-${phaseNumber}.${suffix}`;
3149
+ }
3150
+ getPhaseLabel(activePhase) {
3151
+ if (activePhase.phaseCount) {
3152
+ return `phase ${activePhase.phaseOrdinal}/${activePhase.phaseCount}`;
3153
+ }
3154
+ return `phase ${activePhase.phaseOrdinal}`;
3155
+ }
3156
+ formatElapsed(elapsedMs) {
3157
+ if (elapsedMs < 1000)
3158
+ return `${Math.round(elapsedMs)}ms`;
3159
+ return `${(elapsedMs / 1000).toFixed(1)}s`;
3160
+ }
3161
+ formatRouter(event) {
3162
+ const parts = [
3163
+ event.autorouterName ? `router=${event.autorouterName}` : null,
3164
+ event.autorouterVersion ? `version=${event.autorouterVersion}` : null,
3165
+ event.effort !== undefined ? `effort=${event.effort}` : null
3166
+ ].filter(Boolean);
3167
+ return parts.join(", ");
3168
+ }
3169
+ 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));
3174
+ }
3175
+ countErrors(simpleRouteJson) {
3176
+ const errors = simpleRouteJson?.errors;
3177
+ return Array.isArray(errors) ? errors.length : 0;
3178
+ }
3179
+ getCurrentCircuitJson() {
3180
+ try {
3181
+ return this.rootCircuit?.db?.toArray?.() ?? [];
3182
+ } catch {
3183
+ return [];
3184
+ }
3185
+ }
3186
+ serializeError(error) {
3187
+ if (error instanceof Error) {
3188
+ return { message: error.message, stack: error.stack };
3189
+ }
3190
+ if (typeof error === "string") {
3191
+ return { message: error };
3192
+ }
3193
+ return {
3194
+ message: error?.message ?? "Unknown autorouting error",
3195
+ stack: error?.stack
3196
+ };
3197
+ }
3198
+ log(message) {
3199
+ this.options.log(message);
3200
+ }
3201
+ }
3202
+
3203
+ // lib/shared/importFromUserLand.ts
3204
+ import { createRequire as createRequire2 } from "node:module";
3205
+ import fs2 from "node:fs";
3206
+ import path2 from "node:path";
2805
3207
  async function importFromUserLand(moduleName) {
2806
- const userModulePath = path.join(process.cwd(), "node_modules", moduleName);
2807
- if (fs.existsSync(userModulePath)) {
2808
- const userRequire = createRequire2(path.join(process.cwd(), "noop.js"));
3208
+ const userModulePath = path2.join(process.cwd(), "node_modules", moduleName);
3209
+ if (fs2.existsSync(userModulePath)) {
3210
+ const userRequire = createRequire2(path2.join(process.cwd(), "noop.js"));
2809
3211
  try {
2810
3212
  const resolvedUserPath = userRequire.resolve(moduleName);
2811
3213
  return await import(resolvedUserPath);
@@ -2828,8 +3230,8 @@ async function importFromUserLand(moduleName) {
2828
3230
  }
2829
3231
 
2830
3232
  // lib/shared/register-static-asset-loaders.ts
2831
- import fs2 from "node:fs";
2832
- import path2 from "node:path";
3233
+ import fs3 from "node:fs";
3234
+ import path3 from "node:path";
2833
3235
  var STATIC_ASSET_EXTENSIONS = [
2834
3236
  ".glb",
2835
3237
  ".gltf",
@@ -2854,15 +3256,15 @@ var TEXT_STATIC_ASSET_EXTENSIONS = new Set([
2854
3256
  ".kicad_sch"
2855
3257
  ]);
2856
3258
  var readFileContentForStaticLoader = (filePath) => {
2857
- const ext = path2.extname(filePath).toLowerCase();
3259
+ const ext = path3.extname(filePath).toLowerCase();
2858
3260
  if (TEXT_STATIC_ASSET_EXTENSIONS.has(ext)) {
2859
- return fs2.readFileSync(filePath, "utf-8");
3261
+ return fs3.readFileSync(filePath, "utf-8");
2860
3262
  }
2861
- const buffer = fs2.readFileSync(filePath);
3263
+ const buffer = fs3.readFileSync(filePath);
2862
3264
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
2863
3265
  };
2864
3266
  var getStaticFileLoader = (filePath) => {
2865
- const extWithDot = path2.extname(filePath).toLowerCase();
3267
+ const extWithDot = path3.extname(filePath).toLowerCase();
2866
3268
  const ext = extWithDot.replace(/^\./, "");
2867
3269
  return activePlatformConfig?.staticFileLoaderMap?.[ext] ?? activePlatformConfig?.staticFileLoaderMap?.[extWithDot];
2868
3270
  };
@@ -2876,12 +3278,12 @@ var normalizeStaticFileLoaderResult = (result) => {
2876
3278
  };
2877
3279
  };
2878
3280
  var getBaseUrlFromTsConfig = () => {
2879
- const tsconfigPath = path2.join(process.cwd(), "tsconfig.json");
3281
+ const tsconfigPath = path3.join(process.cwd(), "tsconfig.json");
2880
3282
  try {
2881
- if (!fs2.existsSync(tsconfigPath)) {
3283
+ if (!fs3.existsSync(tsconfigPath)) {
2882
3284
  return null;
2883
3285
  }
2884
- const tsconfigContent = fs2.readFileSync(tsconfigPath, "utf-8");
3286
+ const tsconfigContent = fs3.readFileSync(tsconfigPath, "utf-8");
2885
3287
  const tsconfig = JSON.parse(tsconfigContent);
2886
3288
  if (tsconfig.compilerOptions?.baseUrl) {
2887
3289
  return tsconfig.compilerOptions.baseUrl;
@@ -2908,8 +3310,8 @@ var registerStaticAssetLoaders = (platformConfig) => {
2908
3310
  loader: "object"
2909
3311
  };
2910
3312
  }
2911
- const baseDir = baseUrl ? path2.resolve(process.cwd(), baseUrl) : process.cwd();
2912
- const relativePath = path2.relative(baseDir, args.path).split(path2.sep).join("/");
3313
+ const baseDir = baseUrl ? path3.resolve(process.cwd(), baseUrl) : process.cwd();
3314
+ const relativePath = path3.relative(baseDir, args.path).split(path3.sep).join("/");
2913
3315
  const pathStr = `./${relativePath}`;
2914
3316
  return {
2915
3317
  exports: {
@@ -2948,7 +3350,8 @@ async function generateCircuitJson({
2948
3350
  saveToFile = false,
2949
3351
  platformConfig,
2950
3352
  injectedProps,
2951
- onAsyncEffectStatus
3353
+ onAsyncEffectStatus,
3354
+ autorouterDiagnostics: autorouterDiagnosticsOptions
2952
3355
  }) {
2953
3356
  debug(`Generating circuit JSON for ${filePath}`);
2954
3357
  const React = await importFromUserLand("react");
@@ -2958,12 +3361,14 @@ async function generateCircuitJson({
2958
3361
  const runner = new userLandTscircuit.RootCircuit({
2959
3362
  platform: platformConfig
2960
3363
  });
2961
- const absoluteFilePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(process.cwd(), filePath);
2962
- const projectDir = path3.dirname(absoluteFilePath);
3364
+ const autorouterDiagnostics = new AutorouterDiagnostics(autorouterDiagnosticsOptions);
3365
+ autorouterDiagnostics.attachToRootCircuit(runner);
3366
+ const absoluteFilePath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
3367
+ const projectDir = path4.dirname(absoluteFilePath);
2963
3368
  const resolvedOutputDir = outputDir ?? projectDir;
2964
- const relativeComponentPath = path3.relative(projectDir, absoluteFilePath);
2965
- const baseFileName = outputFileName || path3.basename(absoluteFilePath).replace(/\.[^.]+$/, "");
2966
- const outputPath = path3.join(resolvedOutputDir, `${baseFileName}.circuit.json`);
3369
+ const relativeComponentPath = path4.relative(projectDir, absoluteFilePath);
3370
+ const baseFileName = outputFileName || path4.basename(absoluteFilePath).replace(/\.[^.]+$/, "");
3371
+ const outputPath = path4.join(resolvedOutputDir, `${baseFileName}.circuit.json`);
2967
3372
  debug(`Project directory: ${projectDir}`);
2968
3373
  debug(`Relative component path: ${relativeComponentPath}`);
2969
3374
  debug(`Output path: ${outputPath}`);
@@ -2983,7 +3388,7 @@ async function generateCircuitJson({
2983
3388
  return false;
2984
3389
  if (normalizedFilePath.match(/^\.[^/]/))
2985
3390
  return false;
2986
- if (!ALLOWED_FILE_EXTENSIONS.includes(path3.extname(normalizedFilePath)))
3391
+ if (!ALLOWED_FILE_EXTENSIONS.includes(path4.extname(normalizedFilePath)))
2987
3392
  return false;
2988
3393
  return true;
2989
3394
  },
@@ -3010,14 +3415,16 @@ async function generateCircuitJson({
3010
3415
  loggedAsyncEffectNames.add(asyncEffectName);
3011
3416
  onAsyncEffectStatus?.(asyncEffectName);
3012
3417
  }
3418
+ autorouterDiagnostics.checkTimeout();
3013
3419
  await new Promise((resolve2) => setTimeout(resolve2, 100));
3014
3420
  runner.render();
3015
3421
  }
3016
3422
  runner.emit("renderComplete");
3017
3423
  const circuitJson = await runner.getCircuitJson();
3424
+ autorouterDiagnostics.finalize(circuitJson);
3018
3425
  if (saveToFile) {
3019
3426
  debug(`Saving circuit JSON to ${outputPath}`);
3020
- fs3.writeFileSync(outputPath, JSON.stringify(circuitJson, null, 2));
3427
+ fs4.writeFileSync(outputPath, JSON.stringify(circuitJson, null, 2));
3021
3428
  }
3022
3429
  return {
3023
3430
  circuitJson,
@@ -3028,28 +3435,28 @@ async function generateCircuitJson({
3028
3435
 
3029
3436
  // lib/shared/get-platform-config-with-cli-defaults.ts
3030
3437
  import { createHash } from "node:crypto";
3031
- import fs4 from "node:fs";
3032
- import path4 from "node:path";
3438
+ import fs5 from "node:fs";
3439
+ import path5 from "node:path";
3033
3440
  import { getPlatformConfig } from "@tscircuit/eval/platform-config";
3034
- function createLocalCacheEngine(cacheDir = path4.join(process.cwd(), ".tscircuit", "cache")) {
3441
+ function createLocalCacheEngine(cacheDir = path5.join(process.cwd(), ".tscircuit", "cache")) {
3035
3442
  return {
3036
3443
  getItem: (key) => {
3037
3444
  try {
3038
3445
  const hash = createHash("md5").update(key).digest("hex");
3039
3446
  const keyWithSafeCharacters = key.replace(/[^a-zA-Z0-9]/g, "_");
3040
- const filePath = path4.join(cacheDir, `${keyWithSafeCharacters.slice(keyWithSafeCharacters.length - 10, keyWithSafeCharacters.length)}-${hash}.json`);
3041
- return fs4.readFileSync(filePath, "utf-8");
3447
+ const filePath = path5.join(cacheDir, `${keyWithSafeCharacters.slice(keyWithSafeCharacters.length - 10, keyWithSafeCharacters.length)}-${hash}.json`);
3448
+ return fs5.readFileSync(filePath, "utf-8");
3042
3449
  } catch {
3043
3450
  return null;
3044
3451
  }
3045
3452
  },
3046
3453
  setItem: (key, value) => {
3047
3454
  try {
3048
- fs4.mkdirSync(cacheDir, { recursive: true });
3455
+ fs5.mkdirSync(cacheDir, { recursive: true });
3049
3456
  const hash = createHash("md5").update(key).digest("hex");
3050
3457
  const keyWithSafeCharacters = key.replace(/[^a-zA-Z0-9]/g, "_");
3051
- const filePath = path4.join(cacheDir, `${keyWithSafeCharacters.slice(keyWithSafeCharacters.length - 10, keyWithSafeCharacters.length)}-${hash}.json`);
3052
- fs4.writeFileSync(filePath, value);
3458
+ const filePath = path5.join(cacheDir, `${keyWithSafeCharacters.slice(keyWithSafeCharacters.length - 10, keyWithSafeCharacters.length)}-${hash}.json`);
3459
+ fs5.writeFileSync(filePath, value);
3053
3460
  } catch {}
3054
3461
  }
3055
3462
  };
@@ -3065,15 +3472,15 @@ function getPlatformConfigWithCliDefaults(userConfig) {
3065
3472
  loadFromUrl: async (url) => {
3066
3473
  let fetchUrl = url;
3067
3474
  if (url.startsWith("./") || url.startsWith("../")) {
3068
- const absolutePath = path4.resolve(process.cwd(), url);
3475
+ const absolutePath = path5.resolve(process.cwd(), url);
3069
3476
  fetchUrl = `file://${absolutePath}`;
3070
3477
  } else if (url.startsWith("/")) {
3071
- if (fs4.existsSync(url)) {
3478
+ if (fs5.existsSync(url)) {
3072
3479
  fetchUrl = `file://${url}`;
3073
3480
  } else {
3074
3481
  const relativePath = `.${url}`;
3075
- const absolutePath = path4.resolve(process.cwd(), relativePath);
3076
- if (fs4.existsSync(absolutePath)) {
3482
+ const absolutePath = path5.resolve(process.cwd(), relativePath);
3483
+ if (fs5.existsSync(absolutePath)) {
3077
3484
  fetchUrl = `file://${absolutePath}`;
3078
3485
  } else {
3079
3486
  fetchUrl = `file://${url}`;
@@ -3146,7 +3553,7 @@ var getSimulationSvgAssetsFromCircuitJson = (circuitJson) => {
3146
3553
 
3147
3554
  // lib/shared/compare-images.ts
3148
3555
  import looksSame from "@tscircuit/image-utils/looks-same";
3149
- import fs5 from "node:fs/promises";
3556
+ import fs6 from "node:fs/promises";
3150
3557
  var compareAndCreateDiff = async (buffer1, buffer2, diffPath, createDiff = true) => {
3151
3558
  const b1 = Buffer.from(buffer1);
3152
3559
  const b2 = Buffer.from(buffer2);
@@ -3162,9 +3569,9 @@ var compareAndCreateDiff = async (buffer1, buffer2, diffPath, createDiff = true)
3162
3569
  highlightColor: "#ff00ff",
3163
3570
  tolerance: 2
3164
3571
  });
3165
- await fs5.writeFile(diffPath, diffBuffer);
3572
+ await fs6.writeFile(diffPath, diffBuffer);
3166
3573
  } else {
3167
- await fs5.writeFile(diffPath, buffer2);
3574
+ await fs6.writeFile(diffPath, buffer2);
3168
3575
  }
3169
3576
  }
3170
3577
  return { equal };
@@ -3193,7 +3600,7 @@ var processSnapshotFile = async ({
3193
3600
  cameraPreset,
3194
3601
  pcbLayer
3195
3602
  }) => {
3196
- const relativeFilePath = path5.relative(projectDir, file);
3603
+ const relativeFilePath = path6.relative(projectDir, file);
3197
3604
  const successPaths = [];
3198
3605
  const warningMessages = [];
3199
3606
  const mismatches = [];
@@ -3204,7 +3611,7 @@ var processSnapshotFile = async ({
3204
3611
  let simulationSvgAssets;
3205
3612
  try {
3206
3613
  if (isCircuitJsonFile(file)) {
3207
- const parsed = JSON.parse(fs6.readFileSync(file, "utf-8"));
3614
+ const parsed = JSON.parse(fs7.readFileSync(file, "utf-8"));
3208
3615
  circuitJson = Array.isArray(parsed) ? parsed : [];
3209
3616
  } else {
3210
3617
  const platformConfigWithCliDefaults = getPlatformConfigWithCliDefaults(platformConfig);
@@ -3288,12 +3695,12 @@ var processSnapshotFile = async ({
3288
3695
  } catch (error) {
3289
3696
  const errorMessage = error instanceof Error ? error.message : String(error);
3290
3697
  if (errorMessage.includes("No pcb_board found in circuit JSON")) {
3291
- const fileDir = path5.dirname(file);
3292
- const relativeDir = path5.relative(projectDir, fileDir);
3293
- const snapDir2 = snapshotsDirName ? path5.join(projectDir, snapshotsDirName, relativeDir) : path5.join(fileDir, "__snapshots__");
3294
- const base2 = path5.basename(file).replace(/\.[^.]+$/, "");
3295
- const snap3dPath = path5.join(snapDir2, `${base2}-3d.snap.png`);
3296
- const existing3dSnapshot = fs6.existsSync(snap3dPath);
3698
+ const fileDir = path6.dirname(file);
3699
+ const relativeDir = path6.relative(projectDir, fileDir);
3700
+ const snapDir2 = snapshotsDirName ? path6.join(projectDir, snapshotsDirName, relativeDir) : path6.join(fileDir, "__snapshots__");
3701
+ const base2 = path6.basename(file).replace(/\.[^.]+$/, "");
3702
+ const snap3dPath = path6.join(snapDir2, `${base2}-3d.snap.png`);
3703
+ const existing3dSnapshot = fs7.existsSync(snap3dPath);
3297
3704
  if (existing3dSnapshot) {
3298
3705
  return {
3299
3706
  ok: false,
@@ -3304,7 +3711,7 @@ var processSnapshotFile = async ({
3304
3711
  errorMessage: kleur_default.red(`
3305
3712
  ❌ Failed to generate 3D snapshot for ${relativeFilePath}:
3306
3713
  `) + kleur_default.red(` No pcb_board found in circuit JSON
3307
- `) + kleur_default.red(` Existing snapshot: ${path5.relative(projectDir, snap3dPath)}
3714
+ `) + kleur_default.red(` Existing snapshot: ${path6.relative(projectDir, snap3dPath)}
3308
3715
  `)
3309
3716
  };
3310
3717
  }
@@ -3325,9 +3732,9 @@ var processSnapshotFile = async ({
3325
3732
  }
3326
3733
  }
3327
3734
  }
3328
- const snapDir = snapshotsDirName ? path5.join(projectDir, snapshotsDirName, path5.relative(projectDir, path5.dirname(file))) : path5.join(path5.dirname(file), "__snapshots__");
3329
- fs6.mkdirSync(snapDir, { recursive: true });
3330
- const base = path5.basename(file).replace(/\.[^.]+$/, "");
3735
+ const snapDir = snapshotsDirName ? path6.join(projectDir, snapshotsDirName, path6.relative(projectDir, path6.dirname(file))) : path6.join(path6.dirname(file), "__snapshots__");
3736
+ fs7.mkdirSync(snapDir, { recursive: true });
3737
+ const base = path6.basename(file).replace(/\.[^.]+$/, "");
3331
3738
  const snapshots = [];
3332
3739
  if (!simulationOnly && (pcbOnly || !schematicOnly)) {
3333
3740
  let pcbSnapshotType = "pcb";
@@ -3361,17 +3768,17 @@ var processSnapshotFile = async ({
3361
3768
  for (const snapshot of snapshots) {
3362
3769
  const { type } = snapshot;
3363
3770
  const is3d = type === "3d";
3364
- const snapPath = path5.join(snapDir, `${base}-${type}.snap.${is3d ? "png" : "svg"}`);
3365
- const existing = fs6.existsSync(snapPath);
3771
+ const snapPath = path6.join(snapDir, `${base}-${type}.snap.${is3d ? "png" : "svg"}`);
3772
+ const existing = fs7.existsSync(snapPath);
3366
3773
  const newContentBuffer = snapshot.isBinary ? snapshot.content : Buffer.from(snapshot.content, "utf8");
3367
3774
  const newContentForFile = snapshot.content;
3368
3775
  if (!existing) {
3369
- fs6.writeFileSync(snapPath, newContentForFile);
3370
- successPaths.push(path5.relative(projectDir, snapPath));
3776
+ fs7.writeFileSync(snapPath, newContentForFile);
3777
+ successPaths.push(path6.relative(projectDir, snapPath));
3371
3778
  didUpdate = true;
3372
3779
  continue;
3373
3780
  }
3374
- const oldContentBuffer = fs6.readFileSync(snapPath);
3781
+ const oldContentBuffer = fs7.readFileSync(snapPath);
3375
3782
  let equal;
3376
3783
  let diffPath;
3377
3784
  if (createDiff) {
@@ -3383,16 +3790,16 @@ var processSnapshotFile = async ({
3383
3790
  }
3384
3791
  if (update) {
3385
3792
  if (!forceUpdate && equal) {
3386
- successPaths.push(path5.relative(projectDir, snapPath));
3793
+ successPaths.push(path6.relative(projectDir, snapPath));
3387
3794
  } else {
3388
- fs6.writeFileSync(snapPath, newContentForFile);
3389
- successPaths.push(path5.relative(projectDir, snapPath));
3795
+ fs7.writeFileSync(snapPath, newContentForFile);
3796
+ successPaths.push(path6.relative(projectDir, snapPath));
3390
3797
  didUpdate = true;
3391
3798
  }
3392
3799
  } else if (!equal) {
3393
3800
  mismatches.push(diffPath ? `${snapPath} (diff: ${diffPath})` : snapPath);
3394
3801
  } else {
3395
- successPaths.push(path5.relative(projectDir, snapPath));
3802
+ successPaths.push(path6.relative(projectDir, snapPath));
3396
3803
  }
3397
3804
  }
3398
3805
  return {
@@ -3405,8 +3812,8 @@ var processSnapshotFile = async ({
3405
3812
  };
3406
3813
 
3407
3814
  // lib/project-config/index.ts
3408
- import * as fs7 from "node:fs";
3409
- import * as path6 from "node:path";
3815
+ import * as fs8 from "node:fs";
3816
+ import * as path7 from "node:path";
3410
3817
  import { pathToFileURL as pathToFileURL2 } from "node:url";
3411
3818
 
3412
3819
  // lib/project-config/project-config-schema.ts
@@ -3481,10 +3888,10 @@ var stripWrappingQuotes = (value) => {
3481
3888
  var loadProjectEnv = (projectDir) => {
3482
3889
  const initialEnvKeys = new Set(Object.keys(process.env));
3483
3890
  for (const envFileName of ENV_FILENAMES) {
3484
- const envPath = path6.join(projectDir, envFileName);
3485
- if (!fs7.existsSync(envPath))
3891
+ const envPath = path7.join(projectDir, envFileName);
3892
+ if (!fs8.existsSync(envPath))
3486
3893
  continue;
3487
- const envContent = fs7.readFileSync(envPath, "utf8");
3894
+ const envContent = fs8.readFileSync(envPath, "utf8");
3488
3895
  for (const rawLine of envContent.split(/\r?\n/)) {
3489
3896
  const line = rawLine.trim();
3490
3897
  if (!line || line.startsWith("#"))
@@ -3502,12 +3909,12 @@ var loadProjectEnv = (projectDir) => {
3502
3909
  }
3503
3910
  };
3504
3911
  var loadProjectConfigSync = (projectDir = process.cwd()) => {
3505
- const configPath = path6.join(projectDir, CONFIG_FILENAME);
3506
- if (!fs7.existsSync(configPath)) {
3912
+ const configPath = path7.join(projectDir, CONFIG_FILENAME);
3913
+ if (!fs8.existsSync(configPath)) {
3507
3914
  return null;
3508
3915
  }
3509
3916
  try {
3510
- const configContent = fs7.readFileSync(configPath, "utf8");
3917
+ const configContent = fs8.readFileSync(configPath, "utf8");
3511
3918
  const parsedConfig = JSON.parse(configContent);
3512
3919
  return projectConfigSchema.parse(parsedConfig);
3513
3920
  } catch (error) {
@@ -3518,12 +3925,12 @@ var loadProjectConfigSync = (projectDir = process.cwd()) => {
3518
3925
  var loadProjectConfigModule = async (projectDir) => {
3519
3926
  loadProjectEnv(projectDir);
3520
3927
  for (const configFileName of CONFIG_MODULE_FILENAMES) {
3521
- const configPath = path6.join(projectDir, configFileName);
3522
- if (!fs7.existsSync(configPath))
3928
+ const configPath = path7.join(projectDir, configFileName);
3929
+ if (!fs8.existsSync(configPath))
3523
3930
  continue;
3524
3931
  try {
3525
3932
  const moduleUrl = pathToFileURL2(configPath);
3526
- const stat = fs7.statSync(configPath);
3933
+ const stat = fs8.statSync(configPath);
3527
3934
  moduleUrl.searchParams.set("tsci", String(stat.mtimeMs));
3528
3935
  const importedModule = await import(moduleUrl.href);
3529
3936
  const exportedConfig = importedModule.default ?? importedModule.config ?? importedModule;
@@ -3570,13 +3977,13 @@ var getSnapshotsDir = (projectDir = process.cwd()) => {
3570
3977
  return config?.snapshotsDir;
3571
3978
  };
3572
3979
  var saveProjectConfig = (config, projectDir = process.cwd()) => {
3573
- const configPath = path6.join(projectDir, CONFIG_FILENAME);
3980
+ const configPath = path7.join(projectDir, CONFIG_FILENAME);
3574
3981
  try {
3575
3982
  const configWithSchema = {
3576
3983
  $schema: CONFIG_SCHEMA_URL,
3577
3984
  ...config ?? {}
3578
3985
  };
3579
- fs7.writeFileSync(configPath, JSON.stringify(configWithSchema, null, 2));
3986
+ fs8.writeFileSync(configPath, JSON.stringify(configWithSchema, null, 2));
3580
3987
  return true;
3581
3988
  } catch (error) {
3582
3989
  console.error(`Error saving tscircuit config: ${error}`);