tscircuit/autorouting-dataset-01

This code manages the hardware and electronic components for circuit design and PCB layout, defining components like resistors, capacitors, transistors, connectors, and footprints, and providing tools for component placement, PCB topology, and electrical connections.

Version
1.0.102
License
unset
Stars
4

lib/cli/loadUserAutorouter.ts

import { readFileSync } from "node:fs"
import path from "node:path"
import { pathToFileURL } from "node:url"
import type { SolverConstructor } from "types/run-benchmark/SolverConstructor"

type LoadedAutorouter = {
  solverConstructor: SolverConstructor
  solverName: string
  packageInfo?: {
    name: string
    version: string
  }
}

const AUTOROUTER_PACKAGE = "@tscircuit/capacity-autorouter"

/**
 * Gets package info for the autorouter package.
 */
const detectPackageInfo = (
  autorouterPath: string,
): LoadedAutorouter["packageInfo"] | undefined => {
  try {
    // Search for node_modules containing the autorouter package
    let searchDir = path.dirname(autorouterPath)
    while (searchDir !== path.dirname(searchDir)) {
      try {
        const packageDir = path.join(
          searchDir,
          "node_modules",
          AUTOROUTER_PACKAGE,
        )
        const pkgJsonPath = path.join(packageDir, "package.json")
        const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"))

        return {
          name: AUTOROUTER_PACKAGE,
          version: pkgJson.version,
        }
      } catch {
        // Package not found in this node_modules, try parent
      }
      searchDir = path.dirname(searchDir)
    }
  } catch {
    // Failed to detect package info
  }
  return undefined
}

/**
 * Dynamically imports a user's autorouter file and finds exports containing "Pipeline".
 * Returns the matching constructor and its name.
 *
 * @param autorouterPath - Path to the autorouter file
 * @param requestedSolverName - Optional specific export name to use
 */
const loadUserAutorouter = async (
  autorouterPath: string,
  requestedSolverName?: string | null,
): Promise<LoadedAutorouter> => {
  let exports: Record<string, unknown>

  try {
    // Convert to file URL for proper import
    const fileUrl = pathToFileURL(autorouterPath).href
    const module = await import(fileUrl)
    exports = module
  } catch (error) {
    throw new Error(
      `Failed to import autorouter from "${autorouterPath}": ${(error as Error).message}`,
    )
  }

  // We'll detect package info after we know the solver name
  let packageInfo: LoadedAutorouter["packageInfo"] | undefined

  // Check for a display name map (like solverDisplayNameByConstructor)
  let displayNameMap: Map<unknown, string> | null = null
  for (const [, exportValue] of Object.entries(exports)) {
    if (exportValue instanceof Map) {
      // Check if it maps functions to strings
      const firstEntry = exportValue.entries().next().value
      if (
        firstEntry &&
        typeof firstEntry[0] === "function" &&
        typeof firstEntry[1] === "string"
      ) {
        displayNameMap = exportValue as Map<unknown, string>
        break
      }
    }
  }

  // Helper to get display name for a solver
  const getDisplayName = (solver: unknown, fallback: string): string => {
    if (displayNameMap) {
      const name = displayNameMap.get(solver)
      if (name) return name
    }
    return fallback
  }

  // Collect all potential solvers from exports
  const pipelineExports: Array<{ name: string; value: unknown }> = []

  for (const [exportName, exportValue] of Object.entries(exports)) {
    // Direct function/class export
    if (typeof exportValue === "function" && !exportName.startsWith("_")) {
      const displayName = getDisplayName(exportValue, exportName)
      pipelineExports.push({ name: displayName, value: exportValue })
    }
    // Array of solvers (like SOLVER_CONSTRUCTOR_LIST)
    else if (Array.isArray(exportValue)) {
      for (const item of exportValue) {
        if (typeof item === "function") {
          const displayName = getDisplayName(item, item.name || "unknown")
          pipelineExports.push({ name: displayName, value: item })
        }
      }
    }
  }

  // Also check default export
  if (exports.default) {
    const defaultExport = exports.default
    if (typeof defaultExport === "function") {
      const fn = defaultExport as { name?: string }
      const displayName = getDisplayName(defaultExport, fn.name || "default")
      pipelineExports.push({ name: displayName, value: defaultExport })
    } else if (Array.isArray(defaultExport)) {
      for (const item of defaultExport) {
        if (typeof item === "function") {
          const fn = item as { name?: string }
          const displayName = getDisplayName(item, fn.name || "unknown")
          pipelineExports.push({ name: displayName, value: item })
        }
      }
    }
  }

  // If a specific solver name was requested, try to find it
  if (requestedSolverName) {
    const requestedExport = pipelineExports.find(
      (e) => e.name === requestedSolverName,
    )
    if (!requestedExport) {
      const availableNames = pipelineExports
        .map((e) => e.name)
        .filter((n) => n !== "default")
      throw new Error(
        `Solver "${requestedSolverName}" not found in "${autorouterPath}". ` +
          `Available solvers: ${availableNames.join(", ") || "(none)"}`,
      )
    }
    packageInfo = detectPackageInfo(autorouterPath)
    return {
      solverConstructor: requestedExport.value as SolverConstructor,
      solverName: requestedSolverName,
      packageInfo,
    }
  }

  if (pipelineExports.length === 0) {
    throw new Error(
      `No autorouter found in "${autorouterPath}". ` +
        `Expected an export containing "Pipeline" in its name (e.g., AutoroutingPipeline, MyPipeline).`,
    )
  }

  // If multiple exports found, list them for the user
  if (pipelineExports.length > 1) {
    console.log(
      `Found ${pipelineExports.length} potential solvers: ${pipelineExports.map((e) => e.name).join(", ")}`,
    )
    console.log(
      `Using "${pipelineExports[0].name}". To use a different one, specify it as the second argument.`,
    )
  }

  // Prefer exports with "Pipeline" in the name, otherwise use first found
  const preferredExport =
    pipelineExports.find((e) => e.name.toLowerCase().includes("pipeline")) ??
    pipelineExports[0]

  const solverConstructor = preferredExport.value as SolverConstructor

  // Validate it looks like a solver constructor
  if (typeof solverConstructor !== "function") {
    throw new Error(
      `Export "${preferredExport.name}" is not a class or function. ` +
        `Expected a solver constructor.`,
    )
  }

  packageInfo = detectPackageInfo(autorouterPath)
  return {
    solverConstructor,
    solverName: preferredExport.name,
    packageInfo,
  }
}

export { loadUserAutorouter }