app
JLCPCB Parts Library
Public Made by Adomby adom
Check the JLCPCB / LCSC parts library before you fab — Basic vs Extended tier, LCSC stock and assembly pricing, so you avoid surprise feeder-setup fees. CLI, a Hydrogen app, and a shared backend.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
//! CLI verbs: search, category-search, list, categories, health.
//! All dispatch to the Node backend via `POST /` with `{action, ...params}`.
use crate::jlcpcb;
use crate::{ok, warn, CategoriesArgs, CategorySearchArgs, HealthArgs, ListArgs, SearchArgs};
use serde_json::{json, Value};
fn emit_json(v: &Value) {
// Put OK: on stderr so stdout stays pure JSON for `| jq`.
let count = v.get("components").and_then(|c| c.as_array()).map(|a| a.len()).unwrap_or(0);
let total = v.get("count").or_else(|| v.get("total")).and_then(|n| n.as_u64());
let query = v.get("query").or_else(|| v.get("category")).and_then(|q| q.as_str()).unwrap_or("");
let summary = if let Some(t) = total {
format!("{} components (showing {}, total {})", count, count, t)
} else {
format!("{} components", count)
};
if crate::stderr_is_tty() {
eprintln!("{}{}OK:{} {} ({})", crate::GREEN, crate::BOLD, crate::RESET, summary, query);
} else {
eprintln!("OK: {} ({})", summary, query);
}
println!("{}", serde_json::to_string_pretty(v).unwrap_or_default());
}
fn resolve_or_fail(explicit: Option<String>) -> Result<String, String> {
match jlcpcb::resolve_backend(explicit) {
Some(api) => Ok(api),
None => {
warn("No backend at http://127.0.0.1:8774 and no --api / JLCPCB_API set.");
Err(
"JLCPCB backend unreachable. Point --api / JLCPCB_API at the service URL \
(e.g. https://jlcpcb-<slug>.adom.cloud), or start a local backend with \
`adom-jlcpcb install` + the service/backend/ Node server."
.into(),
)
}
}
}
pub fn run_search(args: SearchArgs) -> Result<(), String> {
let api = resolve_or_fail(args.api)?;
let v = jlcpcb::post_action(
&api,
json!({
"action": "search",
"keyword": args.keyword,
"limit": args.limit,
"inStockOnly": args.in_stock_only,
}),
)?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) { return Err(e.into()); }
emit_json(&v);
Ok(())
}
pub fn run_category_search(args: CategorySearchArgs) -> Result<(), String> {
let api = resolve_or_fail(args.api)?;
let v = jlcpcb::post_action(
&api,
json!({
"action": "category_search",
"keyword": args.keyword,
"category": args.category,
"limit": args.limit,
"inStockOnly": args.in_stock_only,
}),
)?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) { return Err(e.into()); }
emit_json(&v);
Ok(())
}
pub fn run_list(args: ListArgs) -> Result<(), String> {
let api = resolve_or_fail(args.api)?;
let mut body = json!({
"action": "list",
"category": args.category,
"limit": args.limit,
"inStockOnly": args.in_stock_only,
});
if let Some(sc) = args.subcategory {
body["subcategory"] = Value::String(sc);
}
let v = jlcpcb::post_action(&api, body)?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) { return Err(e.into()); }
emit_json(&v);
Ok(())
}
pub fn run_categories(args: CategoriesArgs) -> Result<(), String> {
let api = resolve_or_fail(args.api)?;
let v = jlcpcb::post_action(&api, json!({"action": "categories"}))?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) { return Err(e.into()); }
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_default());
Ok(())
}
pub fn run_health(args: HealthArgs) -> Result<(), String> {
let api = resolve_or_fail(args.api)?;
let v = jlcpcb::get_health(&api)?;
ok(format!("backend {} reachable", api));
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_default());
Ok(())
}