feat: libs 0009-B — footprint serve + read wiring (GPL backend + standalone): extForKind .kicad_mod read/write, kind-filtered listLibs, LibsSource.listLibs(kind) threaded + boot per-tool kind; footprint-browse-remote e2e green (opens real capped 10.0.3 fp); bump pcbjam-shared
This commit is contained in:
parent
19fce866c1
commit
2c52637b1a
10 changed files with 163 additions and 26 deletions
|
|
@ -31,6 +31,13 @@ function safeLibDir(root: string, lib: string): string | null {
|
|||
return path.join(root, lib);
|
||||
}
|
||||
|
||||
/** Per-kind body file extension (one file per item), or null for unknown kinds. */
|
||||
export function extForKind(kind: string): string | null {
|
||||
if (kind === "symbol") return ".kicad_sym";
|
||||
if (kind === "footprint") return ".kicad_mod";
|
||||
return null;
|
||||
}
|
||||
|
||||
interface IndexFile {
|
||||
items?: { kind: string; name: string; description?: string | null; keywords?: string | null }[];
|
||||
description?: string | null;
|
||||
|
|
@ -44,7 +51,10 @@ async function readIndex(dir: string): Promise<IndexFile | null> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function listLibs(cfg: LibsConfig): Promise<Lib[]> {
|
||||
export async function listLibs(
|
||||
cfg: LibsConfig,
|
||||
kind?: string,
|
||||
): Promise<Lib[]> {
|
||||
if (!cfg.dir) return [];
|
||||
let entries: import("node:fs").Dirent[];
|
||||
try {
|
||||
|
|
@ -56,6 +66,9 @@ export async function listLibs(cfg: LibsConfig): Promise<Lib[]> {
|
|||
for (const e of entries) {
|
||||
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
||||
const idx = await readIndex(path.join(cfg.dir, e.name));
|
||||
// Filter origins by item kind when requested (a footprint tool shouldn't
|
||||
// list symbol-only origins, and vice versa).
|
||||
if (kind && !(idx?.items ?? []).some((i) => i.kind === kind)) continue;
|
||||
libs.push({
|
||||
id: e.name,
|
||||
name: e.name,
|
||||
|
|
@ -92,14 +105,15 @@ export function itemBodyPath(
|
|||
kind: string,
|
||||
name: string,
|
||||
): string | null {
|
||||
if (!cfg.dir || kind !== "symbol") return null;
|
||||
const ext = extForKind(kind);
|
||||
if (!cfg.dir || !ext) return null;
|
||||
const dir = safeLibDir(cfg.dir, lib);
|
||||
if (!dir) return null;
|
||||
// Symbol names allow a wide charset but never path separators.
|
||||
// Item names allow a wide charset but never path separators.
|
||||
if (name.includes("/") || name.includes("\\") || name.includes("..")) {
|
||||
return null;
|
||||
}
|
||||
return path.join(dir, `${name}.kicad_sym`);
|
||||
return path.join(dir, `${name}${ext}`);
|
||||
}
|
||||
|
||||
export function streamBody(absPath: string) {
|
||||
|
|
|
|||
|
|
@ -177,10 +177,12 @@ async function main(): Promise<void> {
|
|||
}
|
||||
return { status: 200 as const, body: await walk(PROJECT_DIR) };
|
||||
},
|
||||
listLibs: async ({ headers }) => {
|
||||
listLibs: async ({ headers, query }) => {
|
||||
const owner = ownerOf(headers);
|
||||
// Origins filtered by item kind (?kind); user libs are kind-agnostic
|
||||
// containers → always listed.
|
||||
const [origins, user] = await Promise.all([
|
||||
listLibs(libs),
|
||||
listLibs(libs, query.kind),
|
||||
listUserLibs(userLibs, owner),
|
||||
]);
|
||||
return { status: 200 as const, body: [...origins, ...user] };
|
||||
|
|
|
|||
|
|
@ -5,16 +5,19 @@
|
|||
// plain files under USER_LIBS_DIR, owner-namespaced, in the same per-lib layout
|
||||
// the read side uses:
|
||||
//
|
||||
// <USER_LIBS_DIR>/<owner>/<libId>/index.json { items: [...], type, name }
|
||||
// <USER_LIBS_DIR>/<owner>/<libId>/<Symbol>.kicad_sym the saved body (verbatim)
|
||||
// <USER_LIBS_DIR>/<owner>/<libId>/index.json { items: [...], type, name }
|
||||
// <USER_LIBS_DIR>/<owner>/<libId>/<Symbol>.kicad_sym a saved symbol body
|
||||
// <USER_LIBS_DIR>/<owner>/<libId>/<Footprint>.kicad_mod a saved footprint body
|
||||
//
|
||||
// No parsing: the editor sends fork-native kicad_symbol_lib bytes, stored as-is
|
||||
// (decision: user-saved bodies round-trip without a version shim). Owner comes
|
||||
// from OWNER_HEADER; absent ⇒ "default".
|
||||
// No parsing: the editor sends fork-native bytes, stored as-is (decision:
|
||||
// user-saved bodies round-trip without a version shim). A user lib is a
|
||||
// kind-agnostic container (symbols + footprints can coexist). Owner comes from
|
||||
// OWNER_HEADER; absent ⇒ "default".
|
||||
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import type { Lib, LibItem } from "@pcbjam/shared";
|
||||
import { extForKind } from "./libs.js";
|
||||
|
||||
export const DEFAULT_OWNER = "default";
|
||||
|
||||
|
|
@ -161,11 +164,12 @@ export function userItemBodyPath(
|
|||
name: string,
|
||||
): string | null {
|
||||
const dir = libDir(cfg, owner, lib);
|
||||
if (!dir || kind !== "symbol" || !safeSeg(name)) return null;
|
||||
return path.join(dir, `${name}.kicad_sym`);
|
||||
const ext = extForKind(kind);
|
||||
if (!dir || !ext || !safeSeg(name)) return null;
|
||||
return path.join(dir, `${name}${ext}`);
|
||||
}
|
||||
|
||||
/** Write one symbol body into a user lib + index it. */
|
||||
/** Write one item body (symbol or footprint) into a user lib + index it. */
|
||||
export async function writeUserItem(
|
||||
cfg: UserLibsConfig,
|
||||
owner: string,
|
||||
|
|
@ -174,13 +178,14 @@ export async function writeUserItem(
|
|||
name: string,
|
||||
body: string,
|
||||
): Promise<LibItem> {
|
||||
if (kind !== "symbol") throw new UserLibError(400, "only symbols for now");
|
||||
const ext = extForKind(kind);
|
||||
if (!ext) throw new UserLibError(400, `unsupported kind "${kind}"`);
|
||||
const dir = libDir(cfg, owner, lib);
|
||||
if (!dir || !safeSeg(name)) throw new UserLibError(400, "bad lib or item name");
|
||||
const idx = await readIndex(dir);
|
||||
if (!idx) throw new UserLibError(404, `user library "${lib}" not found`);
|
||||
|
||||
await fs.writeFile(path.join(dir, `${name}.kicad_sym`), body, "utf8");
|
||||
await fs.writeFile(path.join(dir, `${name}${ext}`), body, "utf8");
|
||||
|
||||
const items = idx.items ?? [];
|
||||
const existing = items.find((i) => i.kind === kind && i.name === name);
|
||||
|
|
|
|||
Loading…
Reference in a new issue