← Commit history

Publish 1.1.1

barrett-land ·89ec1983cb ·3mo ago ·parent 13058ef
3 files changed +54−4
package.json+2−2
@@ -4,9 +4,9 @@   "slug": "kicad-library-manager",   "title": "KiCad Library Manager",   "brief": "Organize your KiCad symbol / footprint / 3D libraries fast — three priority views, inline editing, live previews, and one-click sync.",-  "version": "1.1.0",+  "version": "1.1.1",   "visibility": "public",-  "changelog": "## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models",+  "changelog": "## 1.1.1\n- Sync now prunes dead lib-table entries (paths that no longer exist) from sym-lib-table / fp-lib-table, so KiCad stops erroring on launch after a library is deleted. Backs up each table to .klm.bak. Also exposed as POST /api/prune-tables.\n\n## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models",   "discovery_triggers": [     "kicad library manager",     "organize my kicad libraries",
page.json+2−2
@@ -4,7 +4,7 @@   "slug": "kicad-library-manager",   "title": "KiCad Library Manager",   "brief": "Organize your KiCad symbol / footprint / 3D libraries fast — three priority views, inline editing, live previews, and one-click sync.",-  "version": "1.1.0",+  "version": "1.1.1",   "description": "A fast workbench for your personal KiCad libraries. Probes your machine via adom-desktop for your sym-lib-table / fp-lib-table, then gives you three views — Symbol priority (inline-editable ref / footprint / datasheet / LCSC PN / description, with footprint autocomplete and live JLCPCB stock+price on hover), Footprint priority (3D model + the symbols using each footprint), and 3D-model priority. Add parts from the Adom wiki or blank, duplicate symbols, flag deletes (red) and adds (green) with an undo/redo tree, preview symbols + footprints + 3D models with kicad-cli rendering, and sync changes back to disk with reversible backups.",   "dependencies": {},   "tags": [@@ -62,5 +62,5 @@     "lcsc stock and price",     "klm"   ],-  "changelog": "## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models"+  "changelog": "## 1.1.1\n- Sync now prunes dead lib-table entries (paths that no longer exist) from sym-lib-table / fp-lib-table, so KiCad stops erroring on launch after a library is deleted. Backs up each table to .klm.bak. Also exposed as POST /api/prune-tables.\n\n## 1.1.0\n- Footprint previews: pads render above silkscreen with pad numbers on top\n- 3D model preview (STEP→GLB) for footprints and a dedicated 3D Models view\n- Live JLCPCB stock + price on hover over an LCSC part number\n- Pending changes: deletes flag red, adds green, edits outline the changed field orange; header chip shows new / deleted / changed counts\n- Sync is a repeatable one-shot (resets after) with per-library sync; deleted libraries get unregistered from the lib-tables\n- Tabs renamed to Symbols / Footprints / 3D Models" }\ No newline at end of file
server.js+50
@@ -748,6 +748,45 @@ async function unregisterLibs(items) {   }   return out; }+// Remove lib-table entries whose path no longer exists (e.g. a library file the user deleted), so+// KiCad stops erroring on launch. Leaves KiCad stock/project entries (${KICAD*}, ${KIPRJMOD}) alone —+// those resolve via KiCad's own env. Runs on every sync. Backs up each table to .klm.bak.+let DESKTOP_HOME = null;+async function desktopHome() {+  if (DESKTOP_HOME) return DESKTOP_HOME;+  const o = await runDesktopShell('printf %s "$HOME"', 15);+  DESKTOP_HOME = (o || '').trim() || HOME; return DESKTOP_HOME;+}+function expandDesktop(p, home) {+  return (p || '').replace(/^~(?=[\\/]|$)/, home).replace(/\$\{HOME\}/g, home).replace(/\$HOME\b/g, home).replace(/\$\{USERPROFILE\}/g, home);+}+async function pruneDeadLibTableEntries() {+  const tables = (DB.probe && DB.probe.tablesFound) || [];+  const result = { removed: [], tables: [] };+  if (!tables.length) return result;+  const home = await desktopHome();+  for (const tp of tables) {+    const text = await runDesktopShell(`[ -f ${shq(tp)} ] && cat ${shq(tp)} || echo __KLM_NOFILE__`, 30);+    if (!text || text.includes('__KLM_NOFILE__')) continue;+    const checks = [];   // concrete-path entries we can verify (skip ${KICAD*}/${KIPRJMOD}/${KISYS*})+    for (const lib of parseLibTable(text)) {+      if (/\$\{KICAD|\$\{KIPRJMOD\}|\$\{KISYS/i.test(lib.uri)) continue;+      const pth = expandDesktop(lib.uri, home);+      if (/\$\{/.test(pth)) continue;   // unknown custom var — leave it alone+      checks.push({ name: lib.name, path: pth });+    }+    if (!checks.length) continue;+    const probe = checks.map(c => `[ -e ${shq(c.path)} ] || echo ${shq('MISS::' + c.name)}`).join('; ');+    const out = await runDesktopShell(probe, 40);+    const dead = [...new Set(out.split('\n').map(l => l.trim()).filter(l => l.startsWith('MISS::')).map(l => l.slice(6)))];+    if (!dead.length) continue;+    const pats = dead.map(n => `-e ${shq('(name "' + n + '")')}`).join(' ');+    const cmd = `T=${shq(tp)}; cp -f "$T" "$T.klm.bak"; grep -Fv ${pats} "$T" > "$T.klm.tmp" && mv "$T.klm.tmp" "$T" && echo OK`;+    const res = await runDesktopShell(cmd, 40);+    if (/OK/.test(res)) { for (const n of dead) result.removed.push({ table: baseOf(tp), name: n }); result.tables.push(tp); }+  }+  return result;+} async function restoreTrash() {   // move everything in the trash back to its sibling .pretty / model dir (best-effort: by basename mapping).   const trashRoot = kiRoot() + '/' + TRASH_DIRNAME;@@ -811,6 +850,9 @@ async function runSync() {     for (const lib of DB.libs.footprintLibs) if (lib.pendingDelete) unregItems.push({ nick: lib.nick, table: 'fp' });     const unreg = await unregisterLibs(unregItems);     if (unreg.removed.length) log.push(`Unregistered ${unreg.removed.length} library(ies) from the lib-table(s)`);+    // 5b) clean any lib-table entries whose path no longer exists (prevents KiCad launch errors)+    const dead = await pruneDeadLibTableEntries();+    if (dead.removed.length) log.push(`Removed ${dead.removed.length} dead lib-table entry(ies): ${dead.removed.map(d => d.name).join(', ')}`);     // 6) prune the model: drop the now-deleted items, clear the pending-add flags     pruneAfterSync();     // one-shot: reset to idle so the button is immediately ready to sync again@@ -905,6 +947,9 @@ async function syncOneLibrary(mode, nick) {     DB.libs.models3d = DB.libs.models3d.filter(md => !((md.folder || '(root)') === nick && md.pendingDelete));     log.push(`wrote ${n} model(s), removed ${del.length}`);   } else throw new Error('bad mode: ' + mode);+  // every push also cleans dead lib-table entries (paths that no longer exist)+  const dead = await pruneDeadLibTableEntries();+  if (dead.removed.length) log.push(`Removed ${dead.removed.length} dead lib-table entry(ies): ${dead.removed.map(d => d.name).join(', ')}`);   recomputeUsage();   DB.sync.lastSyncedAt = new Date().toISOString(); DB.sync.log = log; save();   return log;@@ -1259,6 +1304,11 @@ const server = http.createServer(async (req, res) => {         return sendJSON(res, 200, { ok: true, opened: true, pushed: !onDisk && !!push });       } catch (e) { return sendJSON(res, 500, { error: e.message }); }     }+    // ---- clean dead lib-table entries (paths that no longer exist) on demand+    if (p === '/api/prune-tables' && req.method === 'POST') {+      try { const r = await pruneDeadLibTableEntries(); return sendJSON(res, 200, { ok: true, removed: r.removed }); }+      catch (e) { return sendJSON(res, 500, { error: e.message }); }+    }     if (p === '/api/sync-library' && req.method === 'POST') {       const { mode, nick } = await readJSON(req);       if (!mode || !nick) return sendJSON(res, 400, { error: 'mode and nick required' });