Skip to content
Open
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
68 changes: 66 additions & 2 deletions cli/search/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ import Fuse from "fuse.js"
import kleur from "kleur"
import { getQueryFromParts } from "cli/utils/get-query-from-parts"

export type MouserSearchResult = {
mouser_product_number: string
supplier_part_number?: string
mfr: string
manufacturer?: string
package?: string
description: string
stock: number
price?: number
product_url?: string
}

export const formatMouserSearchResult = (
component: MouserSearchResult,
index: number,
): string =>
`${index + 1}. ${component.mfr} (${component.mouser_product_number}) - ${component.description} (stock: ${component.stock.toLocaleString("en-US")})`

export const registerSearch = (program: Command) => {
program
.command("search")
Expand All @@ -17,6 +35,7 @@ export const registerSearch = (program: Command) => {
.option("--kicad", "Search KiCad footprints")
.option("--jlcpcb", "Search JLCPCB components")
.option("--lcsc", "Alias for --jlcpcb")
.option("--mouser", "Search Mouser components")
.option("--tscircuit", "Search tscircuit registry packages")
.option("--json", "Output search results as JSON")
.action(
Expand All @@ -26,15 +45,21 @@ export const registerSearch = (program: Command) => {
kicad?: boolean
jlcpcb?: boolean
lcsc?: boolean
mouser?: boolean
tscircuit?: boolean
json?: boolean
},
) => {
const query = getQueryFromParts(queryParts)
const hasFilters =
opts.kicad || opts.jlcpcb || opts.lcsc || opts.tscircuit
opts.kicad ||
opts.jlcpcb ||
opts.lcsc ||
opts.mouser ||
opts.tscircuit
const searchKicad = opts.kicad
const searchJlc = opts.jlcpcb || opts.lcsc || !hasFilters
const searchMouser = opts.mouser
const searchTscircuit = opts.tscircuit

let results: {
Expand All @@ -55,6 +80,8 @@ export const registerSearch = (program: Command) => {
price: number
}> = []

let mouserResults: MouserSearchResult[] = []

let kicadResults: string[] = []

try {
Expand All @@ -75,6 +102,22 @@ export const registerSearch = (program: Command) => {
jlcResults = jlcResponse?.components ?? []
}

if (searchMouser) {
const mouserSearchUrl =
"https://mousersearch.tscircuit.com/api/search?limit=10&q=" +
encodeURIComponent(query)
const mouserResponse = await fetch(mouserSearchUrl, {
headers: { accept: "application/json" },
})
if (!mouserResponse.ok) {
throw new Error(
`Mouser search returned ${mouserResponse.status}: ${await mouserResponse.text()}`,
)
}
const mouserJson = await mouserResponse.json()
mouserResults = mouserJson?.components ?? []
}

if (searchKicad) {
const kicadFiles: string[] = await fetch(
"https://kicad-mod-cache.tscircuit.com/kicad_files.json",
Expand Down Expand Up @@ -107,6 +150,10 @@ export const registerSearch = (program: Command) => {
source: "jlcpcb" as const,
...comp,
})),
...mouserResults.map((comp) => ({
source: "mouser" as const,
...comp,
})),
]

console.log(
Expand All @@ -125,11 +172,13 @@ export const registerSearch = (program: Command) => {
if (
!kicadResults.length &&
!results.packages.length &&
!jlcResults.length
!jlcResults.length &&
!mouserResults.length
) {
const sources = [
searchTscircuit && "tscircuit registry",
searchJlc && "JLCPCB",
searchMouser && "Mouser",
searchKicad && "KiCad",
].filter(Boolean)
console.log(
Expand Down Expand Up @@ -194,6 +243,21 @@ export const registerSearch = (program: Command) => {
)
})
}

if (mouserResults.length) {
console.log()
console.log(
kleur
.bold()
.underline(
`Found ${mouserResults.length} component(s) in Mouser search:`,
),
)

mouserResults.forEach((component, index) => {
console.log(formatMouserSearchResult(component, index))
})
}
console.log("\n")
},
)
Expand Down
68 changes: 68 additions & 0 deletions tests/cli/search/search-mouser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { afterEach, describe, expect, it, spyOn } from "bun:test"
import { Command } from "commander"
import { registerSearch } from "../../../cli/search/register"

const originalFetch = globalThis.fetch

afterEach(() => {
globalThis.fetch = originalFetch
})

describe("search --mouser", () => {
it("queries mousersearch and emits unified JSON results", async () => {
let requestedUrl = ""
globalThis.fetch = (async (input) => {
requestedUrl = String(input)
return new Response(
JSON.stringify({
components: [
{
mouser_product_number: "603-RC0603FR-0710KL",
mfr: "RC0603FR-0710KL",
manufacturer: "YAGEO",
package: "0603 (1608 Metric)",
description: "Thick Film Resistors - SMD 10K ohm 1%",
stock: 1200,
price: 0.01,
},
],
}),
{ status: 200 },
)
}) as typeof fetch

const log = spyOn(console, "log").mockImplementation(() => {})
const program = new Command().name("tsci")
registerSearch(program)

await program.parseAsync([
"node",
"tsci",
"search",
"--mouser",
"--json",
"10k",
"0603",
])

expect(requestedUrl).toBe(
"https://mousersearch.tscircuit.com/api/search?limit=10&q=10k%200603",
)
const output = String(log.mock.calls[0]?.[0])
const parsed = JSON.parse(output)
expect(parsed.query).toBe("10k 0603")
expect(parsed.results).toEqual([
{
source: "mouser",
mouser_product_number: "603-RC0603FR-0710KL",
mfr: "RC0603FR-0710KL",
manufacturer: "YAGEO",
package: "0603 (1608 Metric)",
description: "Thick Film Resistors - SMD 10K ohm 1%",
stock: 1200,
price: 0.01,
},
])
log.mockRestore()
})
})
Loading