Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 78 additions & 3 deletions cli/check/placement/register.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,87 @@
import fs from "node:fs"
import path from "node:path"
import {
analyzeAllPlacements,
analyzeComponentPlacement,
} from "@tscircuit/circuit-json-placement-analysis"
import type { PlatformConfig } from "@tscircuit/props"
import type { AnyCircuitElement } from "circuit-json"
import type { Command } from "commander"
import { generateCircuitJson } from "lib/shared/generate-circuit-json"
import { getCompletePlatformConfig } from "lib/shared/get-complete-platform-config"
import { getEntrypoint } from "lib/shared/get-entrypoint"

const isPrebuiltCircuitJsonFile = (filePath: string) => {
const normalizedInputPath = filePath.toLowerCase().replaceAll("\\", "/")

return (
normalizedInputPath.endsWith(".circuit.json") ||
normalizedInputPath.endsWith("/circuit.json")
)
}

const resolveInputFilePath = async (file?: string) => {
if (file) {
return path.isAbsolute(file) ? file : path.resolve(process.cwd(), file)
}

const entrypoint = await getEntrypoint({
projectDir: process.cwd(),
})

if (!entrypoint) {
throw new Error("No input file provided and no entrypoint found")
}

return entrypoint
}

const getCircuitJsonForPlacementCheck = async (filePath: string) => {
if (isPrebuiltCircuitJsonFile(filePath)) {
const parsedJson = JSON.parse(fs.readFileSync(filePath, "utf-8"))

return Array.isArray(parsedJson) ? parsedJson : []
}

const completePlatformConfig = getCompletePlatformConfig({
pcbDisabled: true,
} satisfies PlatformConfig)

const { circuitJson } = await generateCircuitJson({
filePath,
platformConfig: completePlatformConfig,
})

return circuitJson
}

export const checkPlacement = async (file?: string, refdes?: string) => {
const resolvedInputFilePath = await resolveInputFilePath(file)
const circuitJson = (await getCircuitJsonForPlacementCheck(
resolvedInputFilePath,
)) as AnyCircuitElement[]

const analysis = refdes
? analyzeComponentPlacement(circuitJson, refdes)
: analyzeAllPlacements(circuitJson)

return analysis.getString()
}

export const registerCheckPlacement = (program: Command) => {
program.commands
.find((c) => c.name() === "check")!
.command("placement")
.description("Partially build and validate the placement")
.argument("[refdeses]", "Optional refdeses to scope the check")
.action(() => {
throw new Error("Not implemented")
.argument("[file]", "Path to the entry file")
.argument("[refdes]", "Optional refdes to scope the check")
.action(async (file?: string, refdes?: string) => {
try {
const output = await checkPlacement(file, refdes)
console.log(output)
} catch (error) {
console.error(error instanceof Error ? error.message : String(error))
process.exit(1)
}
})
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "@tscircuit/cli",
"version": "0.1.1018",
"main": "dist/cli/main.js",
"exports": {
".": "./dist/cli/main.js",
"./lib": "./dist/lib/index.js"
},
"version": "0.1.1018",
"devDependencies": {
"@babel/standalone": "^7.26.9",
"@biomejs/biome": "^1.9.4",
"@tscircuit/circuit-json-placement-analysis": "^0.0.1",
"@tscircuit/circuit-json-util": "0.0.72",
"@tscircuit/fake-snippets": "^0.0.182",
"@tscircuit/file-server": "^0.0.32",
Expand Down Expand Up @@ -68,6 +69,10 @@
"peerDependencies": {
"tscircuit": "*"
},
"exports": {
".": "./dist/cli/main.js",
"./lib": "./dist/lib/index.js"
},
"bin": {
"tscircuit-cli": "./cli/entrypoint.js"
},
Expand Down
45 changes: 45 additions & 0 deletions tests/cli/check/check-placement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from "bun:test"
import path from "node:path"
import { writeFile, unlink } from "node:fs/promises"
import { checkPlacement } from "../../../cli/check/placement/register"

const circuitCode = `
export default () => (
<board width="10mm" height="10mm">
<resistor resistance="1k" footprint="0402" name="R1" pcbX={3} pcbY={2} />
<capacitor capacitance="1000pF" footprint="0402" name="C1" pcbX={-3} pcbY={-2} />
</board>
)
`

const makeCircuitFile = async () => {
const circuitPath = path.join(
process.cwd(),
`tmp-check-placement-${Date.now()}-${Math.random().toString(36).slice(2)}.tsx`,
)
await writeFile(circuitPath, circuitCode)
return circuitPath
}

test("check placement analyzes the provided refdes", async () => {
const circuitPath = await makeCircuitFile()

try {
const output = await checkPlacement(circuitPath, "R1")
expect(output).toContain("R1")
} finally {
await unlink(circuitPath)
}
})

test("check placement analyzes all placements when refdes is missing", async () => {
const circuitPath = await makeCircuitFile()

try {
const output = await checkPlacement(circuitPath)
expect(output).toContain("R1")
expect(output).toContain("C1")
} finally {
await unlink(circuitPath)
}
})
Loading