@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.
- package/dist/cli/build/build.worker.js +250 -140
- package/dist/cli/main.js +193 -59
- package/dist/cli/snapshot/snapshot.worker.js +133 -12
- package/dist/lib/index.js +24 -2
- package/package.json +1 -1
|
@@ -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 ??
|
|
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 ${
|
|
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
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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(
|
|
80194
|
+
console.log(kleur_default.cyan(formatAutorouterDebugEvent(event)));
|
|
80173
80195
|
});
|
|
80174
80196
|
}
|
|
80175
80197
|
this.filesystemWatcher = watch(this.projectDir, {
|