From 73c7dd0c5f57f17beec86bbd8837139eeea9dbb1 Mon Sep 17 00:00:00 2001 From: lucaferri-dev Date: Sun, 8 Mar 2026 20:02:38 +0000 Subject: [PATCH] fix: support JLCPCB shop parts by falling back to manufacturer name search When a JLCPCB part number (e.g. C9900037709) isn't found in EasyEDA's LCSC catalog, query the JLCPCB API to get the manufacturer part name and retry the EasyEDA search with that name. Fixes #288. Co-Authored-By: Claude Opus 4.6 --- lib/websafe/fetch-easyeda-json.ts | 72 ++++++++++++++++++- .../c9900037709-jlcpcb-shop-part.test.ts | 11 +++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/fetch-tests/c9900037709-jlcpcb-shop-part.test.ts diff --git a/lib/websafe/fetch-easyeda-json.ts b/lib/websafe/fetch-easyeda-json.ts index 2dd21e45..1ec367b3 100644 --- a/lib/websafe/fetch-easyeda-json.ts +++ b/lib/websafe/fetch-easyeda-json.ts @@ -1,5 +1,60 @@ import type { RawEasyEdaJson } from "../schemas/easy-eda-json-schema" +async function tryJlcpcbFallbackSearch( + jlcpcbPartNumber: string, + searchUrl: string, + searchHeaders: Record, + originalSearchData: string, + fetch: typeof globalThis.fetch, +): Promise<{ success: boolean; result: any } | null> { + try { + const jlcpcbResponse = await fetch( + "https://jlcpcb.com/api/overseas-pcb-order/v1/shoppingCart/smtGood/selectSmtComponentList", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + keyword: jlcpcbPartNumber, + currentPage: 1, + pageSize: 10, + }), + }, + ) + if (!jlcpcbResponse.ok) return null + + const jlcpcbData = await jlcpcbResponse.json() + const parts = jlcpcbData?.data?.componentPageInfo?.list + if (!parts?.length) return null + + const partName = parts[0].erpComponentName + if (!partName) return null + + // Search EasyEDA with the manufacturer part name + const fallbackSearchData = originalSearchData.replace( + `wd=${jlcpcbPartNumber}`, + `wd=${encodeURIComponent(partName)}`, + ) + const fallbackResponse = await fetch(searchUrl, { + method: "POST", + headers: searchHeaders, + body: fallbackSearchData, + }) + if (!fallbackResponse.ok) return null + + const fallbackResult = await fallbackResponse.json() + if ( + !fallbackResult.success || + !fallbackResult.result?.lists?.lcsc?.length + ) { + return null + } + + return fallbackResult + } catch { + return null + } +} + export async function fetchEasyEDAComponent( jlcpcbPartNumber: string, { fetch = globalThis.fetch }: { fetch?: typeof globalThis.fetch } = {}, @@ -41,7 +96,22 @@ export async function fetchEasyEDAComponent( const searchResult = await searchResponse.json() if (!searchResult.success || !searchResult.result.lists.lcsc.length) { - throw new Error("Component not found") + // Try JLCPCB API to get manufacturer part name and retry search + const jlcpcbResult = await tryJlcpcbFallbackSearch( + jlcpcbPartNumber, + searchUrl, + searchHeaders, + searchData, + fetch, + ) + if (jlcpcbResult) { + searchResult.result = jlcpcbResult.result + searchResult.success = jlcpcbResult.success + } else { + throw new Error( + `Component ${jlcpcbPartNumber} not found. This may be a JLCPCB-exclusive part without EasyEDA data.`, + ) + } } const bestMatchComponent = diff --git a/tests/fetch-tests/c9900037709-jlcpcb-shop-part.test.ts b/tests/fetch-tests/c9900037709-jlcpcb-shop-part.test.ts new file mode 100644 index 00000000..17a7a98d --- /dev/null +++ b/tests/fetch-tests/c9900037709-jlcpcb-shop-part.test.ts @@ -0,0 +1,11 @@ +import { expect, it } from "bun:test" +import { fetchEasyEDAComponent } from "lib/websafe/fetch-easyeda-json" + +it("should fetch JLCPCB shop part C9900037709 via manufacturer name fallback", async () => { + const result = await fetchEasyEDAComponent("C9900037709") + + // C9900037709 is a JLCPCB-exclusive TXB0104 part not in EasyEDA/LCSC directly. + // The fallback should find a TXB0104 variant via the manufacturer part name. + expect(result).toBeDefined() + expect(result.dataStr?.head?.c_para?.["Manufacturer Part"]).toMatch(/TXB0104/) +})